Coder Social home page Coder Social logo

mongoose-lean-virtuals's People

Contributors

alexandremagniez avatar edwardsph avatar islandrhythms avatar kikill95 avatar linusbrolin avatar makinde avatar maximilianschmid avatar rdougan avatar stieg avatar thoglen avatar vkarpov15 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

mongoose-lean-virtuals's Issues

Not working with Typescript

When using with

import mongooseLeanVirtuals from "mongoose-lean-virtuals";

it gives the same error as the mongoose-lean-getters plugin: mongoosejs/mongoose-lean-getters#11

/usr/current/app/node_modules/mongoose/lib/schema.js:1406
    throw new Error('First param to `schema.plugin()` must be a function, '

Is there some other way to use this plugin?

Order of functions

Hello; is there any particular order in which I must call lean()?

The following did not work:

Address
            .find()
            .lean({ virtuals: true })
            .or([{
                'name': text
            }, {
                'address': text
            }])
            .and([{
                'addressType': type.toString()
            }])
            .populate('country addressType')
            .exec(function (err, addresses) {
                debugger;
                if (err)
                    res.status(500).send(err);
                else
                    res.status(200).send(addresses);
            });

Error when get a document with discriminator array field from DB

Hi,

If get a document with discriminator array field from database with [email protected], an error will occur even if don't have any virtual fields.

Below is the test code to reproduce the problem:

const mongoose = require('mongoose');
const mongooseLeanVirtuals = require('mongoose-lean-virtuals');
const ObjectId = mongoose.Types.ObjectId;
const Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true, useUnifiedTopology: true });



const testSchema = new Schema({
  title: String,
  values: {
    type: [new Schema({
      fieldId: { type: Schema.Types.ObjectId, required: true },
      value: Schema.Types.Mixed,
    })]
  },
});

testSchema.path('values').discriminator('String', new Schema({
  fieldId: { type: Schema.Types.ObjectId, required: true },
  value: {
    type: String,
  },
}));

testSchema.path('values').discriminator('Multi-String', new Schema({
  fieldId: { type: Schema.Types.ObjectId, required: true },
  value: {
    type: [String],
    required: true
  },
}));

testSchema.plugin(mongooseLeanVirtuals);

const testModel = mongoose.model('test', testSchema);

describe('test', () => {
  before('Clear collection and add sample document', async () => {
    await testModel.deleteMany({});
    await new testModel({
      title: 'test',
      values: [{
        fieldId: new ObjectId(),
        value: 'abc',
        __t: 'String'
      },
      {
        fieldId: new ObjectId(),
        value: [
          'abc',
          'sfadsf',
          'bvbd'
        ],
        __t: 'Multi-String'
      }]
    }).save();
  });
  after('DB disconnect', async () => {
    await mongoose.disconnect();
  });
  it('Can it run without error?', async () => {
    const testDoc = await testModel.findOne({ title: 'test' }).lean({ virtuals: true });
    console.log(JSON.stringify(testDoc));
  });
});

And the error message is:

$ mocha index.js


  test
    1) Can it run without error?


  0 passing (66ms)
  1 failing

  1) test
       Can it run without error?:
     TypeError: Cannot read property 'Multi-String' of undefined
      at findCorrectDiscriminator (node_modules/mongoose-lean-virtuals/index.js:127:52)
      at Array.find (<anonymous>)
      at attachVirtualsToDoc (node_modules/mongoose-lean-virtuals/index.js:125:40)
      at applyVirtualsToResult (node_modules/mongoose-lean-virtuals/index.js:78:7)
      at model.Query.attachVirtuals (node_modules/mongoose-lean-virtuals/index.js:71:10)
      at applyVirtualsToChildren (node_modules/mongoose-lean-virtuals/index.js:111:20)
      at model.Query.attachVirtuals (node_modules/mongoose-lean-virtuals/index.js:70:3)
      at model.Query.<anonymous> (node_modules/mongoose-lean-virtuals/index.js:43:22)
      at next (node_modules/kareem/index.js:198:31)
      at Kareem.execPost (node_modules/kareem/index.js:217:3)
      at _cb (node_modules/kareem/index.js:307:15)
      at /Users/bit072/repo/mongoose-lean-virtuals-discriminator-error/node_modules/mongoose/lib/query.js:4445:12
      at _completeOneLean (node_modules/mongoose/lib/query.js:3635:10)
      at model.Query.Query._completeOne (node_modules/mongoose/lib/query.js:2101:7)
      at Immediate.<anonymous> (node_modules/mongoose/lib/query.js:2146:10)
      at Immediate.<anonymous> (node_modules/mquery/lib/utils.js:116:16)
      at processImmediate (internal/timers.js:456:21)

nodejs: 12.18.0
mongodb: 4.2.7
mongoose: 5.10.1
mongoose-lean-virtuals: 0.6.8
mocha: 8.1.2

BUG: unsupported deep lean with `true`

Do you want to request a feature or report a bug?

I would like to report a bug in that library.

Steps to reproduce.

'use strict'

import assert from 'assert'
import mongoose from 'mongoose'

(async () => {
  try {
    await mongoose.connect('mongodb://localhost:27017/test', {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
      useFindAndModify: false
    })
    
    const plugin = require('mongoose-lean-virtuals')
   
    const T1 = new mongoose.Schema({ t11: String }, { _id: false })
    T1.virtual('t12').get(() => 't12')
    T1.plugin(plugin)
    const T2 = new mongoose.Schema({ t1: T1, t2: String })
    T2.virtual('t21').get(() => 't21')
    T2.plugin(plugin)
    const MT2 = mongoose.model('MT2', T2)
    const reps = new MT2({ t1: { t11: 't11' }, t2: 't2' })
    await reps.save()
    const received = await MT2.findById(reps._id).lean({ virtuals: true }).exec()
    const expected = {
      _id: reps._id,
      id: reps._id.toString(),
      t1: { t11: 't11', t12: 't12' /* virtual */ },
      t2: 't2',
      t21: 't21' /* virtual */,
      __v:0
    }

    /* the following assertion fails */
    assert.deepStrictEqual(received, expected)
    process.exit(0)
  } catch (e) {
    console.info(e)
    process.exit(-1)
  }
})()

Expected behaviour

When passing { virtuals: true }, I would expect that the plugin also applies this to sub-schemas, so that my above virtual field t2.t21 is populated. I would even suggest adding another option, say { deep: true } either for the lean (e.g. lean({ virtuals: true, deep: true })) or suggest a plugin option proper to the schema (e.g. schema.plugin(require('mongoose-lean-virtuals'), { deep: true })). We may even do both so that the option given to the lean() method overrides the one given to the plugin, although not sure whether it is possible with the current framework.

The problem arises from there https://github.com/vkarpov15/mongoose-lean-virtuals/blob/f39b0a66a26432609124f47247400b7b30caca46/index.js#L115-L125 As you can see, when virtuals is set to true, then it does not consider the keys of schema.virtuals.

EDIT: I removed my fix which is not an entirely correct fix. I will need to investigate more but this issue remains as is, I think.

Versions

Node.js: 10.21.0
Mongoose: 5.11.4
mongoose-lean-virtuals: 0.7.5
MongoDB: 4.4.2

Bug: passing nested fields to `virtuals` options does not work when trying to get a virtual on a populated virtual field

The title seems complicated, so is the bug. This specific case does not work for me.

I have a virtual populated field (which is referenced to same model), and I have a virtual field that simply returns a string. The virtual field works on the parent document, but does not work on the child document. I am including the full working script and the current and expected output below.

Gist:
https://gist.github.com/sulaysumaria/2ab2f55363cc56b231f2deaefd96c611

Current output:

{
  id: 'XRd8AEsA0kt9Lqcp6qkD57tf',
  name: 'User two',
  avatarId: 'XRd8AEsA0kt9Lqcp6qkD57tf',
  referencedById: 'JmGvkmbTyPRIdzjny5r5i08k',
  avatar: { id: 'XRd8AEsA0kt9Lqcp6qkD57tf', fileName: 'File one' },
  referencedBy: {
    id: 'JmGvkmbTyPRIdzjny5r5i08k',
    name: 'User one',
    avatarId: 'XRd8AEsA0kt9Lqcp6qkD57tf',
    avatar: { id: 'XRd8AEsA0kt9Lqcp6qkD57tf', fileName: 'File one' }
  },
  virtualField: 'some virtual id'
}

Expected output:

{
  id: 'XRd8AEsA0kt9Lqcp6qkD57tf',
  name: 'User two',
  avatarId: 'XRd8AEsA0kt9Lqcp6qkD57tf',
  referencedById: 'JmGvkmbTyPRIdzjny5r5i08k',
  avatar: { id: 'XRd8AEsA0kt9Lqcp6qkD57tf', fileName: 'File one' },
  referencedBy: {
    id: 'JmGvkmbTyPRIdzjny5r5i08k',
    name: 'User one',
    avatarId: 'XRd8AEsA0kt9Lqcp6qkD57tf',
    avatar: { id: 'XRd8AEsA0kt9Lqcp6qkD57tf', fileName: 'File one' },
    virtualField: 'some virtual id'   // <----- This field should be present but is not.
  },
  virtualField: 'some virtual id'
}

Versions:
mongoose - 6.1.9
mongoose-lean-virtuals - 0.9.0

I found this issue related to the bug I am facing, but the solution mentioned there does not work. #50

Bug : parent() does not work to get up in the object more than once

I have a very nested structure and need have the following getter for one of my object:

const getParent = (doc: Document | any): any => {
  return doc instanceof Document
    ? (doc as Types.Subdocument).parent()
    : mongooseLeanVirtuals.parent(doc);
};

MySchema.virtual("attr").get(function (this: BaseValuesType): number | undefined {
  const parent = getParent(this);
  const parentOfParent = getParent(parent);

  // do something with parentOfParent
};

This works without a problem doing .toObject() on a constructed instance but it fails with parentOfParent being undefined when using lean({ virtuals: true }) on a query.

Leaning subdocuments virtuals with select does not work on last mongoose version

Hi !

With mongoose 5.12.2 and mongoose-lean-virtuals 0.7.6, just found that the next query only lean the root _id value :

 Project.find(match)
            .select('_id name')
            .populate('company', '_id name')
            .lean({ virtuals: ['id'] });  // we only need the id virtual

// Return : 
"{
  "_id": "5cade6a33c27940010c01de2",
  "company": {
    "_id": "5cadc7773c27940010c00298",
    "name": "SOME COMP"
  },
  "name": "A PROJECT",
  "id": "5cade6a33c27940010c01de2"
}"

result.company haven't any "id" property.

From the past, with mongoose 5.9.1 and mongoose-lean-virtuals 0.5.0, the same query return :

"{
  "_id": "5cade6a33c27940010c01de2",
  "company": {
    "_id": "5cadc7773c27940010c00298",
    "name": "SOME COMP",
    "id": "5cadc7773c27940010c00298" 
  },
  "name": "A PROJECT",
  "id": "5cade6a33c27940010c01de2"
}"

Virtual worked fine with subdocument.

However, It work well using .lean({ virtuals: true });.
But we have some virtuals that we dont want to return everytime, and deleting them from each document/subdocument is not suitable.

Is it intended ?

sub-document virtuals in nested arrays don't get attached when specified

Do you want to request a feature or report a bug?

Bug

What is the current behaviour?

The virtuals of sub-documents in nested arrays aren't attached to the result when I specify the virtual paths { virtuals: [...] }. It works fine when I lean the query with { virtuals: true }

If the current behaviour is a bug, please provide the steps to reproduce.

import mongoose from "mongoose";
import { mongooseLeanVirtuals } from "mongoose-lean-virtuals";

mongoose.plugin(mongooseLeanVirtuals);

const NameSchema = new mongoose.Schema({
  first: { type: String, required: true },
  last: String,
});

NameSchema.virtual("full").get(function () {
  return `${this.first} ${this.last ?? ""}`.trim();
});

const ChildSchema = new mongoose.Schema({
  age: { type: Number, required: true },
  name: { type: NameSchema, required: true },
}, { _id: false });

const ParentModel = mongoose.model(
  "Parent",
  new mongoose.Schema({
    name: { type: NameSchema, required: true },
    child: ChildSchema,
    nested: new mongoose.Schema({
      children: [ChildSchema],
    }),
  })
);

async function run() {
  await mongoose.connect("...");

  await ParentModel.create({
    name: { first: "Homer", last: "Simpson" },
    child: { age: 10, name: { first: "Bart", last: "Simpson" } },
    nested: {
      children: [
        { age: 6, name: { first: "Lisa", last: "Simpson" } },
        { age: 3, name: { first: "Baby" } },
      ],
    },
  });

  const result = await ParentModel.find({})
    .populate("child")
    .populate("nested.children")
    .lean({
      virtuals: ["name.full", "child.name.full", "children.name.full"],
    });

  assert.equal(result.name.full, "Homer Simpson"); // Pass
  assert.equal(result.child.name.full, "Bart Simpson"); // Pass
  assert.equal(result.children[0].name.full, "Lisa Simpson"); // Fail
  assert.equal(result.children[1].name.full, "Baby"); // Fail
}

run().catch(console.error)

What is the expected behaviour?

For the virtuals of the sub-documents in nested arrays to be attached.

What are the versions of Node.js, mongoose-lean-getters, and Mongoose are you are using? Note that "latest" is not a version.

Package Version
mongoose 6.4.3
mongoose-lean-getters N/A
Node.js 18.6.0

lean({virtuals: true}) doesn't work with mongoose discriminator

When i have virtual on discriminator it doesn't return the virtuals on the result.
for example:
let's assume we have BaseModel.

The DerivedModel has virtual method called foo.

If we do DerivedModel.find({}).lean({virtuals: true}) we will not get the foo property

Subdocument schema virtuals not coming.

If I have a child schema nested within the main schema, the virtual defined on the child schema doesnt come.

eg.

const mongoose = require('mongoose');
const leanVirtualPlugin = require('mongoose-lean-virtuals');

const childSchema = new mongoose.Schema({
  name: {
    type: String
  }
}, {
  timestamps: true,

  toObject: {
    virtuals: true
  },
  toJSON: {
    virtuals: true
  }
});

childSchema.virtual('something').get(function() {
  return 'something';
});

const mainSchema = new mongoose.Schema({
  name: {
    type: String
  },
  children: [childSchema]
}, {
toObject: {
    virtuals: true
  },
  toJSON: {
    virtuals: true
  }
});

mainSchema.virtual('main').get(function() {
  return 'main';
});

mainSchema.virtual('children.someOtherThing').get(function() {
  return 'someOtherThing';
});

mainSchema.set('collection', 'test');
mainSchema.plugin(leanVirtualPlugin);
const TestModel =  mongoose.model('test', mainSchema);

Now TestModel.find().lean({virtuals:true}).exec() , gives me the 'main' virtual, also 'someOtherThing' virtual as a key of children (not for each array item). But it doesnt give me the child virtual 'something' on each element of the children array. Like -

_id: 5c8955801b7d3179990b2150, name: 'hello', children: [ { _id: 5c8955801b7d3179990b2152, name: 'child1', }, { _id: 5c8955801b7d3179990b2151, name: 'child2', }, someOtherThing: 'someOtherThing' ], main: 'main',

Getter with lean({virtuals: true}) is missing in the result

When i use lean with virtuals: true i would expect the getter will be also reflected in the result.

I couldn't find anything in the documentation so i am not sure how i can tackle this issue.
I am using Decimal128 type and i need to get it as real double to avoid conversions across the code.

Any help will be appreciated.
Thanks

.parent() returns undefined when using .find() against model with nested schemas that has virtuals

Plugin version: 0.7.6
Mongoose version: 5.12.3

Schema:

var variationSchema = mongoose.Schema({
	variation: {type: mongoose.Schema.Types.ObjectId, ref: 'VariationType'}, 
	optionsAvailable: 
		[{
			label: String, 
			SKUFragment: String,
			sortOrder: Number
		}]
});

var stockSchema = mongoose.Schema({
		map: [Number],
		SKU: {type: String, index: true},
		amount: Number,
		price: Number,
		images: {
	            truncatedForBrevity: String
		},
		display: {type:Boolean, default: true}
}, schemaOptions);

var MainSchema = mongoose.Schema({
  name: String,

  categories: [{type: mongoose.Types.ObjectId, ref: 'StoreCategory', index: true}],
  
  onSale: {type: Boolean, default: true},
  availableOnline: {type: Boolean, default: true},

  SKUPrefix: String,
  basePrice: Number,
  description: String,
  variations: [variationSchema],
  images: { truncatedForBrevity: String },

  stock: [stockSchema],

  deleted: {type: Boolean, default: false},

}, schemaOptions);

stockSchema.virtual('label').get(function() {
	let parent;

	if (this instanceof mongoose.Document) {
		parent = this.parent();
	} else {
		parent = mongooseLeanVirtuals.parent(this);
	}

	const variations = parent.variations;

	let labelArray = [];
	this.map.forEach(function (v, index) {
		let variation = variations[index];
		let type = variation.optionsAvailable[v];
		labelArray.push(type.label);
	});

	return labelArray.join(', ');
});

stockSchema.label is a virtual getter that's supposed to iterate through its map property and identify it against the values found its parent's (MainSchema) property `variation' and assemble a label.

With a valid sample JSON object using the above schema looking like this (I removed most of the irrelevant properties):

{
  "_id": "60673d22b68709e3a4393e75",
  "variations": [
    {
      "_id": "60673d22b68709e3a4393e76",
      "variation": {
        "_id": "5fda879333124842b57bf2df",
        "label": "Size",
        "isSize": true
      },
      "optionsAvailable": [
        {
          "_id": "60673d22b68709e3a4393e77",
          "label": "Extra Small",
          "SKUFragment": "XS",
          "sortOrder": 0,
          "id": "60673d22b68709e3a4393e77"
        },
        {
          "_id": "60673d22b68709e3a4393e78",
          "label": "Small",
          "SKUFragment": "SM",
          "sortOrder": 1,
          "id": "60673d22b68709e3a4393e78"
        },
      ],
      "id": "60673d22b68709e3a4393e76"
    },
    {
      "_id": "60673d22b68709e3a4393e7c",
      "variation": {
        "_id": "5fda879333124842b57bf2e0",
        "label": "Color"
      },
      "optionsAvailable": [
        {
          "_id": "60673d22b68709e3a4393e7d",
          "label": "Blue",
          "SKUFragment": "BLU",
          "sortOrder": 0,
          "id": "60673d22b68709e3a4393e7d"
        },
        {
          "_id": "60673d22b68709e3a4393e7e",
          "label": "Black",
          "SKUFragment": "BLK",
          "sortOrder": 1,
          "id": "60673d22b68709e3a4393e7e"
        }
      ],
      "id": "60673d22b68709e3a4393e7c"
    }
  ],
  "stock": [
    {
      "map": [0, 0],
      "display": true,
      "_id": "6075017f1824dc2460a3f481",
      "amount": 5,
      "price": 800,
      "SKU": "BYF2L-XS-BLU",
      "images": {
        "main": "s3:///store/uploads/BYF2L/BYF2L-LG-BLU/main.jpg",
      },
      "label": "Extra Small, Blue",
      "id": "6075017f1824dc2460a3f481"
    }
  ],
  "name": "Mongoose Tee",
  "description": "This awesome t-shirt shows that you love mongodb and mongoose!",
}

Using .findOne().lean({virtuals: true}) the .label virtual getter works fine. However, with .find().lean({virtuals: true}) it fails because in stockSchema.label the call to mongooseLeanVirtuals.parent(this) returns undefined.

Stepping through the applyVirtualsToResult() call in the plugin:

      if (parent != null && res[i] != null && typeof res[i] === 'object') {
        documentParentsMap.set(res[i], parent);
      }

Using find(): Both res[i] and parent are each an array of arrays of objects. (Fail case)
Using findOne(): Both res[i] and parent are each arrays of objects. (Success case)

Plugin doesn't work with subdocuments when specifying virtuals explicitly

Hello,

When my documents have subdocuments (lets call it subs) the function attachVirtuals is called twice, once for each set of documents. By defaults, the virtuals to apply are taken from the (sub)schema and then everything works. However, this doesn't allow me to specify which virtuals I would like to include. When I specify an array of virtuals explicitly (lets use [rootVirtual]) then I would expect rootVirtual to be set on the root documents and not on the set of subdocuments as the virtual doesn't start with subs., instead the virtual is being applied on both set of documents. This results in an error as the subdocument schema doesn't have this virtual.

Any way to solve this issue?

Best

Feature: Ability to default to true

Would be a big benefit to be able to default adding virtuals to true. This way only the model has to be updated and all existing usage of .lean() will work with the new virtual. Currently, all usages have to be updated to include {virtuals: true}.

Plugin usage with QueryCursor.eachAsync

Hello,

In our codebase we were doing

ourModel.find(OUR_QUERY)
   .lean({ virtuals: true })
   .cursor()
   .eachAsync(p => {
      // p is not a POJO but a Model instance
      // w/ save, update... methods
   });

Still as the comment in the callback suggest, p is not a POJO (and it should be as stated here).

This is not a bug related to this plugin since mongoose itselft checks the explicit true value of the lean options before hitting the eachAsync callback (here).

Should the verification done by mongoose check the truthyness of the lean option instead of the explicit true value ?
Or should this be marked as the expected behavior in the documentation of this plugin ?

Also how to get the virtuals in the async w/ the lean 🤔 (dropping the lean and using .toObject() works, but it makes no use of the lean feature...)

In any case, I'ld be glad to help :)

Thank you.

edit: checking the truthyness in mongoose would not help because the .cursor method does not trigger post find hooks, which this plugin relies on.

Fails when the query uses populate for referenced sub-docs

I've been using this library for a while with just {virtuals: true} and found it really helpful but I need to limit the number of virtuals being returned so attempted to replace true with an array of names. It almost works but I was getting an error when using this in a query that populates referenced docs. The line below fails because applyGetters is called against an undefined value:

cur[sp[sp.length - 1]] = schema.virtuals[virtual].applyGetters(cur[sp[sp.length - 1]], doc);

Simply testing to see if schema.virtuals[virtual] exists before this line seems to have worked. One concern I have is that the virtuals provided in the list are not specific to any particular schema, The code will give you virtuals from every sub-schema where a virtual matches the name. That is fine once understood but it would be really nice to be able to be more specific. For example if you have a student record with a contact record you might want to specify virtuals as ['currentStatus', 'contact.fullName'] to get the first from the top level schema and the second from the specific child schema.

virtuals population not working with cursor() in mongoose 5

I had a piece of code that did

let query = Model.find({ foo: 'bar' }).setOptions( opts ).lean({ virtuals: true })
let cursor = query.cursor()
//...
let doc = cursor.next()

And in 4.x it worked with virtuals populated. Not so in mongoose 5.x.

Thanks!

RangeError: Maximum call stack size exceeded , when query with lean({virtuals:true})

This is my console log.
image

The following code is not working

const mongoose = require('mongoose');
const mongooseLeanVirtuals = require('mongoose-lean-virtuals');
const { paginate } = require('mongoose-paginate-v2');

//const opts = { toJSON: { virtuals: true } };

const post = mongoose.Schema({
    user_id: String,
    title: String,
    content: String,
    media: {
        default: "",
        type: String
    },
    tag: [
        String
    ],
    created_time: {
        default: Date.now,
        type: Date
    },
    reactions: [
        {
            user_id: String,
            reaction_type: String
        }
    ],

    permission: {
        type: String,
        default: "open"
    }

})
const commentSchema = mongoose.Schema({
    user_id: String,
    content: String,
    media: {
        type: String,
        default:""
    },
    reactions: [
        {
            user_id: String,
            reaction_type: String
        }
    ],
    created_time: {
        default: Date.now,
        type: Date
    },
})
commentSchema.add({
    comments: [
        {
            commentSchema
        }
    ],
})
post.add({
    comments: [
        commentSchema
    ]
})

commentSchema.virtual('comment_id').get(function () {
    return this._id;
});
commentSchema.plugin(mongooseLeanVirtuals);

post.virtual('post_id').get(function () {
    return this._id;
});
post.plugin(mongooseLeanVirtuals);

module.exports = mongoose.model('post', post);

parent() documentation

I'm having the same exact issue as #40, but I wasn't able to find any documentation/example on how the parent() function is supposed to be used.

I would greatly appreciate it if you could point me to an example of how to use it.

Thanks

No support for .parent() or .ownerDocument()?

We have a virtual on a child schema that needs visibility to the parent document.

ChildSchema.virtual('hasAThing').get(function () {
  const parent = this.parent(); // fails!
  return parent.arrayOfThings.some(
    (thing) => thing.status === 'valid' and thing.items.includes(this._id)
  );
});

ParentSchema = new Schema(
{
   _id: { type: ObjectId, required: true },
   arrayOfThings: [ChildSchema]
});

ParentSchema.plugin(mongooseLeanVirtuals);
ParentSchema.set('toJSON', { virtuals: true });
ParentSchema.set('toObject', { virtuals: true });

I was hoping (expecting?) that the ability to see .parent() (or .ownerDocument()) from Mongoose would work here as documented in other places e.g. https://mongoosejs.com/docs/subdocs.html#subdoc-parents

But these are undefined. Should they be? Is there a way of accessing the parent document as part of leanvirtuals?

Stack trace that happens:

Object. (/myProject/src/my.model.js:101:20)
at VirtualType.applyGetters (/myProject/src/node_modules/mongoose/lib/virtualtype.js:142:25)
at attachVirtualsToDoc (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:148:55)
at attachVirtualsToDoc (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:119:7)
at applyVirtualsToResult (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:78:7)
at model.Query.attachVirtuals (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:71:10)
at applyVirtualsToChildren (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:111:20)
at model.Query.attachVirtuals (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:70:3)
at model.Query. (/myProject/src/node_modules/mongoose-lean-virtuals/index.js:43:22)
at /myProject/src/node_modules/mongoose-lean-virtuals/index.js:11:12

(the error occurs on the line where I attempt to call .parent() or .ownerDocument())

How do I reference the parent document from a child virtual when using this library?

Mongoose ver 5.10.2
Mongoose-lean-virtuals ver 0.6.9

Using mongoose model with cursor returns undefined

Hello, and thank you for a great plugin

I've encountered an issue, when I am using cursor

Few code of example (migrations, so I do not care about performance)

  let db = await require('../../util/db').connect()
  console.log('Started ...')
  try {
    const cursor = DocModel.find().cursor()
    for (let doc = await cursor.next(); doc; doc = await cursor.next()) {
      console.log('Processing document...', doc._id)
      try {
        await doc.save() // trigger hooks
      } catch (err) {
        console.log('Could not execute:', err)
      }
    }
  } catch (error) {
    console.error(error)
  }
  console.log('Finished ...')
  await db.disconnect()

And in model:

const DocModelSchema = new mongoose.Schema({
  // ...
})
DocModelSchema.plugin(mongoosePaginate)
module.exports = mongoose.model('DocModel', DocModelSchema)

When I am commenting DocModelSchema.plugin(mongoosePaginate) - everything works as expected

Chaining getter on virtual doesn't work with lean

We have the following virtual defined in our schema:

AnnotationTypeSchema.virtual('instances', {
    ref: AnnotationType.name,
    localField: 'classToAnnotate',
    foreignField: 'classToAnnotate',
    match: { textField: { $exists: true } },
}).get((instances: BaseAnnotationType[]) => instances.map(instance => instance.textField));

And we're trying to execute the following query:

const items = await this.annotationTypeModel
            .find(searchFilter)
            .limit(filter.limit)
            .skip(filter.limit * (filter.page - 1))
            .sort({ title: 1 })
            .populate('instances')
            .lean({ virtuals: true, getters: true })
            .exec();

But we get the error, that the instances is undefined and therefore the map function cannot be executed.

Don't know if the intended behavior of the Plugin should cover such cases.

v0.4.0 release breaks 'find' operation

It seems that v0.4.0 has broken population of virtuals in child schemas when doing a find operation. To demonstrate take the 'with nested schemas' unit test and replace the findOne() operation with a find({}) operation like so:

  it('with nested schemas (gh-20)', function() {
    const schema = new mongoose.Schema({ name: String });
    schema.virtual('lower').get(function() {
      return this.name.toLowerCase();
    });

    const parentSchema = new mongoose.Schema({
      nested: schema,
      arr: [schema]
    });
    parentSchema.plugin(mongooseLeanVirtuals);

    const Model = mongoose.model('gh20', parentSchema);

    return co(function*() {
      yield Model.create({
        nested: { name: 'FOO' },
        arr: [{ name: 'BAR' }, { name: 'BAZ' }]
      });

      const doc = (yield Model.find({}).lean({ virtuals: true }))[0];

      assert.equal(doc.nested.lower, 'foo');
      assert.equal(doc.arr[0].lower, 'bar');
      assert.equal(doc.arr[1].lower, 'baz');
    });
  });

If you run npm test you will get the following stack:

    TypeError: Cannot read property 'toLowerCase' of undefined
      at Array.<anonymous> (test/index.test.js:89:24)
      at VirtualType.applyGetters (node_modules/mongoose/lib/virtualtype.js:118:25)
      at attachVirtualsToDoc (index.js:80:55)
      at model.Query.attachVirtuals (index.js:47:9)
      at model.Query.attachVirtuals (index.js:61:22)
      at model.Query.<anonymous> (index.js:20:20)
      at next (node_modules/kareem/index.js:198:31)
      at Kareem.execPost (node_modules/kareem/index.js:217:3)
      at _cb (node_modules/kareem/index.js:307:15)
      at /home/stieg/Devel/airfordable/mongoose-lean-virtuals/node_modules/mongoose/lib/query.js:4099:12
      at cb (node_modules/mongoose/lib/query.js:1787:9)
      at result (node_modules/mongodb/lib/utils.js:414:17)
      at executeCallback (node_modules/mongodb/lib/utils.js:406:9)
      at handleCallback (node_modules/mongodb/lib/utils.js:128:55)
      at cursor.close (node_modules/mongodb/lib/operations/cursor_ops.js:224:62)
      at handleCallback (node_modules/mongodb/lib/utils.js:128:55)
      at completeClose (node_modules/mongodb/lib/cursor.js:893:14)
      at _endSession (node_modules/mongodb/lib/cursor.js:904:37)
      at Cursor._endSession (node_modules/mongodb-core/lib/cursor.js:195:5)
      at Cursor._endSession (node_modules/mongodb/lib/cursor.js:226:59)
      at Cursor.close (node_modules/mongodb/lib/cursor.js:904:19)
      at cursor._next (node_modules/mongodb/lib/operations/cursor_ops.js:224:23)
      at handleCallback (node_modules/mongodb-core/lib/cursor.js:204:5)
      at _setCursorNotifiedImpl (node_modules/mongodb-core/lib/cursor.js:433:38)
      at self._endSession (node_modules/mongodb-core/lib/cursor.js:441:46)
      at ClientSession.endSession (node_modules/mongodb-core/lib/sessions.js:129:41)
      at Cursor._endSession (node_modules/mongodb-core/lib/cursor.js:190:13)
      at Cursor._endSession (node_modules/mongodb/lib/cursor.js:226:59)
      at _setCursorNotifiedImpl (node_modules/mongodb-core/lib/cursor.js:441:17)
      at setCursorNotified (node_modules/mongodb-core/lib/cursor.js:433:3)
      at setCursorDeadAndNotified (node_modules/mongodb-core/lib/cursor.js:426:3)
      at nextFunction (node_modules/mongodb-core/lib/cursor.js:554:5)
      at Cursor.next (node_modules/mongodb-core/lib/cursor.js:763:3)
      at Cursor._next (node_modules/mongodb/lib/cursor.js:211:36)
      at fetchDocs (node_modules/mongodb/lib/operations/cursor_ops.js:217:12)
      at cursor._next (node_modules/mongodb/lib/operations/cursor_ops.js:243:7)
      at handleCallback (node_modules/mongodb-core/lib/cursor.js:204:5)
      at nextFunction (node_modules/mongodb-core/lib/cursor.js:585:5)
      at done (node_modules/mongodb-core/lib/cursor.js:651:7)
      at queryCallback (node_modules/mongodb-core/lib/cursor.js:699:18)
      at /home/stieg/Devel/airfordable/mongoose-lean-virtuals/node_modules/mongodb-core/lib/connection/pool.js:532:18
      at process._tickCallback (internal/process/next_tick.js:61:11)

Error in 0.7.0 - weakmap key error

When upgrading to 0.7.0 I'm getting the following error due to

TypeError: Invalid value used as weak map key
    at WeakMap.set (<anonymous>)
    at applyVirtualsToResult (/root/repo/node_modules/mongoose-lean-virtuals/index.js:88:28)
    at model.Query.attachVirtuals (/root/repo/node_modules/mongoose-lean-virtuals/index.js:80:10)
    at applyVirtualsToChildren (/root/repo/node_modules/mongoose-lean-virtuals/index.js:126:20)
      if (parent != null) {
        documentParentsMap.set(res[i], parent);
      }

Note that I was the asker for the .parent function, but this code is not yet using it.

The issue is that there are code paths where this could be called but there's no results. E.g. the fix is to

      if (parent != null) {
          if (res[i]) {
            documentParentsMap.set(res[i], parent);
          }
      }

I confirmed with logging that sometimes res[i] is undefined and thus we need a truthy check.

Virtual field in array of array can't be attached

Hi,

The virtual field in array of array can't be attached in mongoose-lean-virtuals@0.6.7, but it is okay in and before 0.6.5 version.

Below is the test code to reproduce the problem:

const chai = require("chai");
const expect = chai.expect;

const mongoose = require('mongoose');
const mongooseLeanVirtuals = require('mongoose-lean-virtuals');
const Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });

const subArraySchema = new Schema({
  name: String,
});

subArraySchema.virtual('lowercase')
  .get(function () {
    return this.name.toLowerCase();
  });


const arraySchema = new Schema({
  subArray:  [subArraySchema],
})

const testSchema = new Schema({
  title: String,
  array: [arraySchema],
});

testSchema.plugin(mongooseLeanVirtuals);

const testModel = mongoose.model('test', testSchema);

describe('test', () => {
  before('Clear collection and add sample document', async () => {
    await testModel.deleteMany({});
    await new testModel({
      title: 'test',
      array: [{
        subArray: [{
          name: 'TEST',
        }]
      }]
    }).save();
  });
  after('DB disconnect', async () => {
    await mongoose.disconnect();
  });
  it('Check if virtual fields exist after lean', async () => {
    const testDoc = await testModel.findOne({ title: 'test' }).lean({ virtuals: true });
    const subObject = testDoc.array[0].subArray[0];
    expect(subObject.name).to.be.equal('TEST');
    expect(subObject.lowercase).to.be.equal('test');
  });
});

And the test result is:

$ mocha index.js
(node:39410) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.


  test
    1) Check if virtual fields exist after lean


  0 passing (85ms)
  1 failing

  1) test
       Check if virtual fields exist after lean:
     AssertionError: expected undefined to equal 'test'
      at Context.<anonymous> (index.js:51:39)
      at processTicksAndRejections (internal/process/task_queues.js:97:5)

nodejs: 12.18.0
mongodb: 4.2.7
mongoose: 5.9.21
mongoose-lean-virtuals: 0.6.7
chai: 4.2.0
mocha: 8.0.1

Discriminators don't work if query result is an array

Hi there,

Thanks for the plugin. I just noticed that virtuals in discriminators don't work if the result of a query is an array.

The problem is how you check which discriminator schema to apply on line 43 - 51. In attachVirtuals, res can be an array, and each of the documents can have a different schema. This method, however, only works for single documents.

I will submit a PR for this, including unit tests.

mongoose lean not overwrite

mongoose(4.9.5) lean method is this
`
Query.prototype.lean = function(v) {

this._mongooseOptions.lean = arguments.length ? !!v : true;

return this;

};
`
lean function just get true or false, can't pass other params.
It's not work for me

populated virtuals

I'm not able to use virtuals on populated models. I'm guessing that is not possible yet?

How to add a virtual field to mongoose array of objects in a schema?

I have a mongoose schema as follow

const mongoose = require("mongoose");
const mongooseLeanVirtuals = require('mongoose-lean-virtuals');
const ImageSchema = new mongoose.Schema({
  img: { type:String }
});
ImageSchema.virtual('img').get(function() {
  return `${this.img}/crop/720/720`;
});
ImageSchema.plugin(mongooseLeanVirtuals);

const PostSchema = new mongoose.Schema(
  {
    title:{
        type: String,
        required: true,
        trim: true
    },
    author:{
      type: mongoose.Schema.Types.ObjectId,
      ref:'Customer'
    },
    images: [ImageSchema]
  },
  { timestamps: true }
);
module.exports = mongoose.model("Post", PostSchema);

Data result is

[
    {
        "_id": "64cb808d95a1ec6708a8e5e6",
        "title": "First Post",
        "author":{...},
        "images": [],
        "createdAt": "2023-08-03T10:33:31.253Z",
        "updatedAt": "2023-08-03T10:33:31.253Z",
        "__v": 0
    },
    {
        "_id": "64cb829a95a1ec6708a8f9ab",
        "title": "Next Post",
        "author":{...},
        "images": [
            {
                "_id": "64cb829a95a1ec6708a8f9ac",
                "img": "image001.png"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ad",
                "img": "image002.png"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ae",
                "img": "image003.png"
            }
        ],
        "createdAt": "2023-08-03T10:34:02.173Z",
        "updatedAt": "2023-08-03T10:34:02.173Z",
        "__v": 0
    }
]

In the database I already have a bunch of data, So I need to modify new image path in the return query as
img : "image003.png/crop/720/720"

Is it possible to achive this using mongoose virtual ?

await Post.find({}).lean({ virtuals: true }).lean().populate('author')

My expected result is

[
    {
        "_id": "64cb808d95a1ec6708a8e5e6",
        "title": "First Post",
        "author":{...},
        "images": [],
        "createdAt": "2023-08-03T10:33:31.253Z",
        "updatedAt": "2023-08-03T10:33:31.253Z",
        "__v": 0
    },
    {
        "_id": "64cb829a95a1ec6708a8f9ab",
        "title": "Next Post",
        "author":{...},
        "images": [
            {
                "_id": "64cb829a95a1ec6708a8f9ac",
                "img": "image001.png/crop/720/720"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ad",
                "img": "image002.png/crop/720/720"
            },
            {
                "_id": "64cb829a95a1ec6708a8f9ae",
                "img": "image001.png/crop/720/720"
            }
        ],
        "createdAt": "2023-08-03T10:34:02.173Z",
        "updatedAt": "2023-08-03T10:34:02.173Z",
        "__v": 0
    }
]

feature: child access to parent's virtual

Do you want to request a feature or report a bug?
bug/feature

What is the current behavior?
parent's lean virtual is undefined

If the current behavior is a bug, please provide the steps to reproduce.

const assert = require('assert');
const mongoose = require('mongoose');
const mongooseLeanVirtuals = require('.');

const main = async () => {
  await mongoose.connect('mongodb://localhost/my_database');

  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/${this._id}`;
  });

  const parentSchema = new mongoose.Schema({
    child: childSchema
  });
  parentSchema.virtual('uri').get(function() {
    return `/parent/${this._id}`;
  });
  
  parentSchema.plugin(mongooseLeanVirtuals);
  
  const Parent = mongoose.model('Parent', parentSchema);
  
  const doc = {
    child: { }
  };

  await Parent.create(doc);

  Parent
    .findOne()
    .lean({ virtuals: true })
    .then(result => {
      assert.equal(result.child.uri, `${result.uri}/child/${result.child.id}`);
    });
};

main().catch(err => {
  console.error(err);
  process.exit(-1);
});
AssertionError [ERR_ASSERTION]: 'undefined/child/630c9d381351fdedf4eecb04' == '/parent/630c9d381351fdedf4eecb03/child/630c9d381351fdedf4eecb04'
    at ...
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: 'undefined/child/630c9d381351fdedf4eecb04',
  expected: '/parent/630c9d381351fdedf4eecb03/child/630c9d381351fdedf4eecb04',
  operator: '=='
}

What is the expected behavior?
parent's lean virtual is available

What are the versions of Node.js, mongoose-lean-getters, and Mongoose are you are using? Note that "latest" is not a version.

.lean does not work with mongoose 5.0.0

Hi,

.lean with virtual was working fine when using mongoose 4.0. After updating it to mongoose 5.0.0, we get this error while using .lean with virtuals.

"Error: this.get is not a function",

Error when discriminator base model has middleware that queries the base model

Reproduction script below. If commenting out the validator, then the test passes. This issue is only present when this plugin is used.

const assert = require('assert')
const mongoose = require('mongoose')
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')

const Schema = mongoose.Schema

it('Should work with discriminators that have middleware that query the base model', async () => {
   // Setup our base model, with a virtual
   const mediaSchema = new Schema(
      { file: String },
      { discriminatorKey: 'kind' }
   )

   mediaSchema.virtual('fileExtension').get(function () {
      return this.file.split('.').pop()
   })

   // * Having this validator causes mongoose-lean-virtuals to fail! *
   // Specifically, querying using the base model is what causes the error
   mediaSchema.path('file').validate(async function (file) {
      const Media = this.constructor
      const mediaDoc = await Media.findOne({
         _id: { $ne: this._id },
         file
      })

      return !mediaDoc
   }, 'The media file you are creating already exists')

   // Everything works okay if we don't register the mongoose-lean-virtuals plugin
   mediaSchema.plugin(mongooseLeanVirtuals)

   // Discriminated schema
   const photoSchema = new Schema({
      position: String
   }, { discriminatorKey: 'kind' })

   const Media = mongoose.model('test-media', mediaSchema)
   const Photo = Media.discriminator('photo', photoSchema)

   // Create new Photo doc
   let photo = new Photo({
      file: 'cover.jpg',
      position: 'left'
   })

   // This fails!
   await photo.save()
   assert.equal(photo.kind, 'photo')
   assert.equal(photo.file, 'cover.jpg')
   assert.equal(photo.position, 'left')
   assert.equal(photo.fileExtension, 'jpg')

   const result = await Media.findOne({}).lean({ virtuals: true }).exec()
   assert.equal(result.fileExtension, 'jpg')
})

The following error is thrown:
ValidationError: photo validation failed: file: Cannot read property 'kind' of null

Using

  • mongoose-lean-virtuals: 0.6.2
  • mongoose: 5.9.12
  • node: 14.2.0

Virtuals called on null sub-document field

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
If a sub-document is null and there is a virtual on it the sub-document is initialized as an object with the virtual instead of stay null

If the current behavior is a bug, please provide the steps to reproduce.
Consider the following setup:

import mongoose from "mongoose";
import { mongooseLeanVirtuals } from "mongoose-lean-virtuals";

mongoose.plugin(mongooseLeanVirtuals);

const ChildSchema = new mongoose.Schema({
  age: { type: Number }
}, { _id: false })

const UserSchema = new mongoose.Schema({
  firstName: { type: String, required: true },
  lastName: { type: String, required: true },
  child: { type: ChildSchema }
});

UserSchema.virtual('child.parentName').get(function () {
  return `${this.firstName} ${this.lastName}`;
});

const UserModel = mongoose.model('users', UserSchema);

run().catch(console.error);

And the following run function:

async function run() {
  await mongoose.connect('');

  await UserModel.create({
    firstName: 'Homer',
    lastName: 'Simpson'
  });

  const result: any = await UserModel.findOne({}).lean({ virtuals: true }).exec();

  assert.equal(result.firstName, 'Homer'); // Pass
  assert.equal(result.lastName, 'Simpson'); // Pass
  assert.equal(result.child, null); // Fail

  console.log(result);
}

As you can see I expect that child field will be null but actually the value is: { parentName: 'Homer Simpson' }.
If I tweak the create a bit like that:

  await UserModel.create({
    firstName: 'Homer',
    lastName: 'Simpson',
    child: null
  });

Then I get a runtime error Cannot read properties of null which is even worst.

What is the expected behavior?
I expect that if a field is null then no virtual should run on it.

What are the versions of Node.js, mongoose-lean-getters, and Mongoose are you are using? Note that "latest" is not a version.

Package Version
mongoose 6.5.0
mongoose-lean-getters N/A
mongoose-lean-virtuals 0.9.1
Node.js 16.13.0

how to set lean default with typegoose

image

In typegoose,
i want to no typing '{ virtuals : true }' but plugin can auto lean it.
when i use the mongooseLeanDefaults, i can use

@plugin(mongooseLeanDefaults as any, { defaults: true })
@plugin(mongooseLeanVirtuals as any, {
  enabledByDefault: true,
  virtuals: true,
  lean: true,
})
export class SyncBBRecord{
 @prop({ default: 'default_name' })
  name?: string;

  get vv() {
    return 'vv';
  }
}

const result = await this.mSyncBBRecord.findOne({}).lean();
console.log(result.name); // name has default value.
console.log(result.vv); // vv is virtuals.

the mongooseLeanDefaults plugin can auto lean,
but mongooseLeanVirtuals cannot.

i hope mongooseLeanVirtuals can esay use just like use mongooseLeanDefaults .

Cannot read property 'length' of null error after upgrading to v0.7.4

TypeError: Cannot read property 'length' of null
    at applyVirtualsToChildren (C:\Users\usr\dev\ew\node_modules\mongoose-lean-virtuals\index.js:127:26)
    at model.Query.attachVirtuals (C:\Users\usr\dev\ew\node_modules\mongoose-lean-virtuals\index.js:79:3)
    at model.Query.<anonymous> (C:\Users\usr\dev\ew\node_modules\mongoose-lean-virtuals\index.js:52:22)
    at C:\Users\usr\dev\ew\node_modules\mongoose-lean-virtuals\index.js:13:12
    at C:\Users\usr\dev\ew\node_modules\mongoose\lib\query.js:4429:15
    at cb (C:\Users\usr\dev\ew\node_modules\mongoose\lib\query.js:1907:9)
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\utils.js:677:5
    at handleCallback (C:\Users\usr\dev\ew\node_modules\mongodb\lib\utils.js:102:55)
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\cursor.js:838:66
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\utils.js:677:5
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\cursor.js:923:9
    at Cursor._endSession (C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:394:7)
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\cursor.js:921:12
    at maybePromise (C:\Users\usr\dev\ew\node_modules\mongodb\lib\utils.js:665:3)
    at Cursor.close (C:\Users\usr\dev\ew\node_modules\mongodb\lib\cursor.js:914:12)
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\cursor.js:838:27
    at handleCallback (C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:31:5)
    at C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:680:38
    at _setCursorNotifiedImpl (C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:693:10)
    at setCursorNotified (C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:680:3)
    at setCursorDeadAndNotified (C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:673:3)
    at nextFunction (C:\Users\usr\dev\ew\node_modules\mongodb\lib\core\cursor.js:827:5)

Virtuals fail to access child schema virtuals after retrieval

The problem:

When you have a schema that contains another schema with virtual properties, virtuals on the parent schema that access a child schema's virtuals do not behave properly.

For example, take two schemas, Foo and Bar.

  • Bar has a number property and a doubleNumber virtual property.
  • Foo has an array of Bar objects, and a virtual (barDoubleTotal) that sums it's bars' doubleNumber values

When you create them a Foo with a couple of child Bars, foo.bar[0].doubleNumber and foo.barDoubleTotal work as expected. After storage and retrieval foo.bar[0].doubleNumber works as expected, but foo.barDoubleTotal returns NaN instead of summing the array of bar's doubleNumber virtual properties.

Code below to (hopefully) clearly illustrate the issue...

Example:

const mongoose = require('mongoose');
const mongooseLeanVirtuals = require('mongoose-lean-virtuals');

// Bar Model
const barSchema = new mongoose.Schema({
    number: { default: 2, type: Number }
});

barSchema.plugin(mongooseLeanVirtuals);

barSchema.virtual('doubleNumber').get(function() {
    return this.number * 2;
});

const Bar = mongoose.model('Bar', barSchema);

// Foo Model
const fooSchema = new mongoose.Schema({
    bars: [Bar.schema],
    number: { type: Number }
});

fooSchema.plugin(mongooseLeanVirtuals);

// virtual that totals virtuals
fooSchema.virtual('barDoubleTotal').get(function() {
    return this.bars.map(b => b.doubleNumber).reduce((a, c) => a + c);
});

fooSchema.virtual('barTotal').get(function() {
    return this.bars.map(b => b.number).reduce((a, c) => a + c);
});

fooSchema.virtual('doubleNumber').get(function() {
    return this.number * 2;
});

const Foo = mongoose.model('Foo', fooSchema);


var bar1 = new Bar({ number: 1 });
var bar2 = new Bar({ number: 3 });

var foo = new Foo({ 
    bars: [ bar1, bar2 ],
    number: 5
});

console.log('\nInitial state:\n***************');
console.log(`✅ plain property\tfoo.number: ${foo.number}`);
console.log(`✅ virtual property\tfoo.doubleNumber: ${foo.doubleNumber}`);
console.log(`✅ sum child properties\tfoo.barTotal: ${foo.barTotal}`);
console.log(`✅ sum child virtuals\tfoo.barDoubleTotal: ${foo.barDoubleTotal}`);
console.log(`✅ child virtual\t\tfoo.bars[0].doubleNumber: ${foo.bars[0].doubleNumber}`);
console.log(`✅ child virtual\t\tfoo.bars[1].doubleNumber: ${foo.bars[1].doubleNumber}`);

console.log('\nstoring...');

mongoose.connect('mongodb://127.0.0.1/nested-virtuals', function() {
    foo.save(function(err, savedFoo) {
        if (err) {
            console.err(err);
            process.exit(1);
        }

        console.log('retrieving...');
        Foo.findOne({_id: savedFoo._id}).lean({ virtuals: true }).exec(function(err, retrievedFoo) {
            if (err) {
                console.err(err);
                process.exit(1);
            }

            console.log('\nRetrieved state:\n***************');
            console.log(`✅ retrievedFoo.number: ${retrievedFoo.number}`);
            console.log(`✅ retrievedFoo.doubleNumber: ${retrievedFoo.doubleNumber}`);
            console.log(`✅ retrievedFoo.barTotal: ${retrievedFoo.barTotal}`);
            console.log(`❌ retrievedFoo.barDoubleTotal: ${retrievedFoo.barDoubleTotal}`);
            console.log(`✅ retrievedFoo.bars[0].doubleNumber: ${retrievedFoo.bars[0].doubleNumber}`);
            console.log(`✅ retrievedFoo.bars[1].doubleNumber: ${retrievedFoo.bars[1].doubleNumber}`);
    
            console.log('\nDone.\n');
            process.exit(0);
        });
    });
});

Output

Initial state:
***************
✅ plain property        foo.number: 5
✅ virtual property      foo.doubleNumber: 10
✅ sum child properties  foo.barTotal: 4
✅ sum child virtuals    foo.barDoubleTotal: 8
✅ child virtual         foo.bars[0].doubleNumber: 2
✅ child virtual         foo.bars[1].doubleNumber: 6

storing...
(node:39847) DeprecationWarning: `open()` is deprecated in mongoose >= 4.11.0, use `openUri()` instead, or set the `useMongoClient` option if using `connect()` or `createConnection()`. See http://mongoosejs.com/docs/4.x/docs/connections.html#use-mongo-client
(node:39847) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html
retrieving...

Retrieved state:
***************
✅ retrievedFoo.number: 5
✅ retrievedFoo.doubleNumber: 10
✅ retrievedFoo.barTotal: 4
❌ retrievedFoo.barDoubleTotal: NaN
✅ retrievedFoo.bars[0].doubleNumber: 2
✅ retrievedFoo.bars[1].doubleNumber: 6

Done.

(Edited for brevity)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.