Skip to content

Commit

Permalink
Merge pull request #73 from mongoosejs/vkarpov15/gh-64
Browse files Browse the repository at this point in the history
fix: allow calling `parent()` on parent, allow accessing parent's virtual from child subdoc
  • Loading branch information
vkarpov15 authored Sep 3, 2024
2 parents 7669db4 + 7f06420 commit 6739f69
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 10 deletions.
10 changes: 6 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
declare module "mongoose-lean-virtuals" {
import mongoose = require('mongoose');
export default function mongooseLeanVirtuals(schema: mongoose.Schema<any, any, any, any>, opts?: any): void;
export function mongooseLeanVirtuals(schema: mongoose.Schema<any, any, any, any>, opts?: any): void;
}
import mongoose = require('mongoose');
export default function mongooseLeanVirtuals(schema: mongoose.Schema<any, any, any, any>, opts?: any): void;
export function mongooseLeanVirtuals(schema: mongoose.Schema<any, any, any, any>, opts?: any): void;

export function parent(value: any): any;
}
30 changes: 25 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const flat = require('array.prototype.flat');
const mpath = require('mpath');

const documentParentsMap = new WeakMap();
const attachVirtualsFnMap = new WeakMap();

module.exports = function mongooseLeanVirtuals(schema, options) {
const fn = attachVirtualsMiddleware(schema, options);
Expand Down Expand Up @@ -32,7 +33,14 @@ module.exports.parent = function(obj) {
if (obj == null) {
return void 0;
}
return documentParentsMap.get(obj);
const res = documentParentsMap.get(obj);
// Since we do we apply virtuals to children before parents,
// we may need to call `applyVirtuals()` on the parent if we're
// accessing the parent from the child.
if (attachVirtualsFnMap.get(res)) {
attachVirtualsFnMap.get(res)();
}
return res;
};

function attachVirtualsMiddleware(schema, options = {}) {
Expand Down Expand Up @@ -75,9 +83,19 @@ function attachVirtuals(schema, res, virtuals, parent) {
}
}

let called = false;
const applyVirtualsToResultOnce = () => {
if (called) {
return;
}
called = true;
applyVirtualsToResult(schema, res, toApply);
};

addToParentMap(res, parent, applyVirtualsToResultOnce);

applyVirtualsToChildren(this, schema, res, virtualsForChildren, parent);
addToParentMap(res, parent);
return applyVirtualsToResult(schema, res, toApply);
return applyVirtualsToResultOnce();
}

function applyVirtualsToResult(schema, res, toApply) {
Expand All @@ -92,8 +110,8 @@ function applyVirtualsToResult(schema, res, toApply) {
}
}

function addToParentMap(res, parent) {
if (parent == null || res == null) {
function addToParentMap(res, parent, attachVirtualsToResult) {
if (res == null) {
return;
}

Expand All @@ -108,13 +126,15 @@ function addToParentMap(res, parent) {
for (const _res of res) {
if (_res != null && typeof _res === 'object') {
documentParentsMap.set(_res, parent);
attachVirtualsFnMap.set(_res, attachVirtualsToResult);
}
}
return;
}

if (typeof res === 'object') {
documentParentsMap.set(res, parent);
attachVirtualsFnMap.set(res, attachVirtualsToResult);
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"acquit-ignore": "0.1.0",
"acquit-markdown": "0.1.0",
"co": "4.6.0",
"eslint": "6.8.0",
"eslint": "7.x",
"istanbul": "0.4.5",
"mocha": "5.2.x",
"mongoose": "7.0.0-rc0"
Expand Down
81 changes: 81 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,4 +743,85 @@ describe('Discriminators work', () => {
assert.equal(doc.lowercase, 'test testerson');
});
});

it('supports parent() on deeply nested docs (gh-65)', function() {
const getParent = (doc) => {
if (doc instanceof mongoose.Document) {
return doc.parent();
}
return mongooseLeanVirtuals.parent(doc);
};

const grandchildSchema = new mongoose.Schema({ firstName: String });
grandchildSchema.virtual('fullName').get(function() {
return `${this.firstName} ${getParent(getParent(this)).lastName}`;
});

const childSchema = new mongoose.Schema({ firstName: String, child: grandchildSchema });
childSchema.virtual('fullName').get(function() {
return `${this.firstName} ${getParent(this).lastName}`;
});

const parentSchema = new mongoose.Schema({
firstName: String,
lastName: String,
child: childSchema
}, { id: false });

parentSchema.plugin(mongooseLeanVirtuals);
const Parent = mongoose.model('gh65', parentSchema);

return co(function*() {
const { _id } = yield Parent.create({
firstName: 'Anakin',
lastName: 'Skywalker',
child: {
firstName: 'Luke',
child: {
firstName: 'Ben'
}
}
});
const doc = yield Parent.findById(_id).lean({ virtuals: true }).orFail();
assert.equal(doc.child.fullName, 'Luke Skywalker');
assert.equal(doc.child.child.fullName, 'Ben Skywalker');
});
});

it('can access parent virtuals from child subdocument (gh-64)', async function() {
const childSchema = new mongoose.Schema({ firstName: String });
childSchema.virtual('uri').get(function() {
// This `uri` virtual is in a subdocument, so in order to get the
// parent's `uri` you need to use this plugin's `parent()` function.

const parent = this instanceof mongoose.Document
? this.parent()
: mongooseLeanVirtuals.parent(this)
;
return `${parent.uri}/child/gh-64-child`;
});

const parentSchema = new mongoose.Schema({
child: childSchema
});
parentSchema.virtual('uri').get(function() {
return `/parent/gh-64-parent`;
});

parentSchema.plugin(mongooseLeanVirtuals);

const Parent = mongoose.model('gh64', parentSchema);

const doc = { child: {} };

await Parent.create(doc);

let result = await Parent
.findOne()
.lean({ virtuals: true });
assert.equal(
result.child.uri,
'/parent/gh-64-parent/child/gh-64-child'
);
});
});

0 comments on commit 6739f69

Please sign in to comment.