Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow calling parent() on parent, allow accessing parent's virtual from child subdoc #73

Merged
merged 2 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
85 changes: 83 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ describe('Discriminators work', () => {

it('sets empty array if no result and justOne: false', async function() {
const childSchema = new mongoose.Schema({ name: String, parentId: 'ObjectId' });
const Child = mongoose.model('C2', childSchema);
mongoose.model('C2', childSchema);

const parentSchema = new mongoose.Schema({ name: String });
parentSchema.virtual('children', {
Expand Down Expand Up @@ -688,7 +688,7 @@ describe('Discriminators work', () => {
const Parent = mongoose.model('Parent2', parentSchema);

const child = await Child.create({ name: 'Luke', surname: { name: 'Skywalker' } });
const parent = await Parent.create({ role: 'Father', surname: { name: 'Vader' }, allegiance: { name: 'Empire' }, child: child });
await Parent.create({ role: 'Father', surname: { name: 'Vader' }, allegiance: { name: 'Empire' }, child: child });
let doc = await Parent.findOne().populate('child').lean({ virtuals: true });
assert.ok(childGetterCalled);
assert.ok(parentGetterCalled);
Expand Down Expand Up @@ -744,4 +744,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'
);
});
});
Loading