Skip to content

Commit

Permalink
feat(model): allow calling hydrate() with { setters: true }
Browse files Browse the repository at this point in the history
Fix #11653
  • Loading branch information
vkarpov15 committed Jul 18, 2022
1 parent 34e00fb commit 491521e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 46 deletions.
8 changes: 7 additions & 1 deletion lib/document.js
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions lib/model.js
Expand Up @@ -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) {
Expand All @@ -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;
};

Expand Down
13 changes: 13 additions & 0 deletions test/model.test.js
Expand Up @@ -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 } }
Expand Down
86 changes: 43 additions & 43 deletions types/models.d.ts
Expand Up @@ -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<mongodb.AnyBulkWriteOperation>, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback<mongodb.BulkWriteResult>): void;
bulkWrite(writes: Array<mongodb.AnyBulkWriteOperation>, callback: Callback<mongodb.BulkWriteResult>): void;
bulkWrite(writes: Array<mongodb.AnyBulkWriteOperation>, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise<mongodb.BulkWriteResult>;

/**
* 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<Document>, options?: mongodb.BulkWriteOptions & { timestamps?: boolean }): Promise<mongodb.BulkWriteResult>;

/** Collection the model uses. */
Expand All @@ -170,10 +170,10 @@ declare module 'mongoose' {
create<DocContents = AnyKeys<T>>(doc: T | DocContents, callback: Callback<HydratedDocument<T, TMethodsAndOverrides, TVirtuals>>): 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<T extends mongodb.Document>(options: mongodb.CreateCollectionOptions & Pick<SchemaOptions, 'expires'> | null, callback: Callback<mongodb.Collection<T>>): void;
createCollection<T extends mongodb.Document>(callback: Callback<mongodb.Collection<T>>): void;
createCollection<T extends mongodb.Document>(options?: mongodb.CreateCollectionOptions & Pick<SchemaOptions, 'expires'>): Promise<mongodb.Collection<T>>;
Expand All @@ -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<T>, options?: QueryOptions<T>, callback?: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethodsAndOverrides, TVirtuals>, TQueryHelpers, T>;
deleteMany(filter: FilterQuery<T>, callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethodsAndOverrides, TVirtuals>, TQueryHelpers, T>;
deleteMany(callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethodsAndOverrides, TVirtuals>, 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<T>, options?: QueryOptions<T>, callback?: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethodsAndOverrides, TVirtuals>, TQueryHelpers, T>;
deleteOne(filter: FilterQuery<T>, callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethodsAndOverrides, TVirtuals>, TQueryHelpers, T>;
deleteOne(callback: CallbackWithoutResult): QueryWithHelpers<mongodb.DeleteResult, HydratedDocument<T, TMethodsAndOverrides, TVirtuals>, 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<ResultDoc = HydratedDocument<T, TMethodsAndOverrides, TVirtuals>>(
id: any,
projection?: ProjectionType<T> | null,
Expand Down Expand Up @@ -240,19 +240,19 @@ declare module 'mongoose' {
): QueryWithHelpers<ResultDoc | null, ResultDoc, TQueryHelpers, T>;

/**
* 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<T, TMethodsAndOverrides, TVirtuals>;
* 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<T, TMethodsAndOverrides, TVirtuals>;

/**
* 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<HydratedDocument<T, TMethodsAndOverrides, TVirtuals>>;

/** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */
Expand Down

0 comments on commit 491521e

Please sign in to comment.