Skip to content

Commit

Permalink
Merge pull request #14352 from Automattic/IslandRhythms/deeply-hydrat…
Browse files Browse the repository at this point in the history
…e-doc

Island rhythms/deeply hydrate doc
  • Loading branch information
vkarpov15 committed Feb 14, 2024
2 parents 47fad95 + 6dafa54 commit db247da
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 8 deletions.
8 changes: 2 additions & 6 deletions lib/document.js
Expand Up @@ -694,7 +694,6 @@ Document.prototype.$__init = function(doc, opts) {
init(this, doc, this._doc, opts);

markArraySubdocsPopulated(this, opts.populated);

this.$emit('init', this);
this.constructor.emit('init', this);

Expand All @@ -703,7 +702,6 @@ Document.prototype.$__init = function(doc, opts) {
null;

applyDefaults(this, this.$__.selected, this.$__.exclude, hasIncludedChildren, false, this.$__.skipDefaults);

return this;
};

Expand Down Expand Up @@ -746,7 +744,6 @@ function init(self, obj, doc, opts, prefix) {
}
path = prefix + i;
schemaType = docSchema.path(path);

// Should still work if not a model-level discriminator, but should not be
// necessary. This is *only* to catch the case where we queried using the
// base model and the discriminated model has a projection
Expand All @@ -770,15 +767,14 @@ function init(self, obj, doc, opts, prefix) {
}
} else {
// Retain order when overwriting defaults
if (doc.hasOwnProperty(i) && obj[i] !== void 0) {
if (doc.hasOwnProperty(i) && obj[i] !== void 0 && !opts.hydratedPopulatedDocs) {
delete doc[i];
}
if (obj[i] === null) {
doc[i] = schemaType._castNullish(null);
} else if (obj[i] !== undefined) {
const wasPopulated = obj[i].$__ == null ? null : obj[i].$__.wasPopulated;

if (schemaType && !wasPopulated) {
if ((schemaType && !wasPopulated) && !opts.hydratedPopulatedDocs) {
try {
if (opts && opts.setters) {
// Call applySetters with `init = false` because otherwise setters are a noop
Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Expand Up @@ -3830,6 +3830,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
* @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
* @param {Boolean} [options.hydratedPopulatedDocs=false] if true, populates the docs if passing pre-populated data
* @return {Document} document instance
* @api public
*/
Expand All @@ -3843,7 +3844,6 @@ Model.hydrate = function(obj, projection, options) {
}
obj = applyProjection(obj, projection);
}

const document = require('./queryHelpers').createModel(this, obj, projection);
document.$init(obj, options);
return document;
Expand Down
18 changes: 18 additions & 0 deletions test/model.hydrate.test.js
Expand Up @@ -99,5 +99,23 @@ describe('model', function() {
assert.equal(hydrated.test, 'test');
assert.deepEqual(hydrated.schema.tree, C.schema.tree);
});
it('should deeply hydrate the document with the `hydratedPopulatedDocs` option (gh-4727)', async function() {
const userSchema = new Schema({
name: String
});
const companySchema = new Schema({
name: String,
users: [{ ref: 'User', type: Schema.Types.ObjectId }]
});

db.model('UserTestHydrate', userSchema);
const Company = db.model('CompanyTestHyrdrate', companySchema);

const users = [{ _id: new mongoose.Types.ObjectId(), name: 'Val' }];
const company = { _id: new mongoose.Types.ObjectId(), name: 'Booster', users: [users[0]] };

const C = Company.hydrate(company, null, { hydratedPopulatedDocs: true });
assert.equal(C.users[0].name, 'Val');
});
});
});
20 changes: 20 additions & 0 deletions test/types/models.test.ts
Expand Up @@ -880,3 +880,23 @@ async function gh13999() {
}
}
}

function gh4727() {
const userSchema = new mongoose.Schema({
name: String
});
const companySchema = new mongoose.Schema({
name: String,
users: [{ ref: 'User', type: mongoose.Schema.Types.ObjectId }]
});

mongoose.model('UserTestHydrate', userSchema);
const Company = mongoose.model('CompanyTestHyrdrate', companySchema);

const users = [{ _id: new mongoose.Types.ObjectId(), name: 'Val' }];
const company = { _id: new mongoose.Types.ObjectId(), name: 'Booster', users: [users[0]] };

return Company.hydrate(company, {}, { hydratedPopulatedDocs: true });


}
7 changes: 6 additions & 1 deletion types/models.d.ts
Expand Up @@ -37,6 +37,11 @@ declare module 'mongoose' {
skipValidation?: boolean;
}

interface HydrateOptions {
setters?: boolean;
hydratedPopulatedDocs?: boolean;
}

interface InsertManyOptions extends
PopulateOption,
SessionOption {
Expand Down Expand Up @@ -371,7 +376,7 @@ declare module 'mongoose' {
* 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 }): THydratedDocumentType;
hydrate(obj: any, projection?: AnyObject, options?: HydrateOptions): THydratedDocumentType;

/**
* This function is responsible for building [indexes](https://www.mongodb.com/docs/manual/indexes/),
Expand Down

0 comments on commit db247da

Please sign in to comment.