mongoosejs / mongoose-lean-getters

Apply getters on lean() documents: https://mongoosejs.com/docs/tutorials/lean.html
Apache License 2.0
10 stars 15 forks source link

Support Virtual Getters #2

Closed makinde closed 4 years ago

makinde commented 4 years ago

Previously, only getters on "real" schema fields we added. Leaving off getter for virtual and virtually populated fields. This adds those back.

makinde commented 4 years ago

@vkarpov15 Here's a broader description of what I'm facing.

I have a model with a virtual populate field. It also has a getter. When I do a lean query, I want that getter to be run and the corresponding value returned. Here's the relevant code snippet.

  const firstJoined = schema.virtual('firstJoined', {
    ref: 'OrgMembership',
    localField: '_id',
    foreignField: 'profile',
    justOne: true,
    match: {
      role: { $in: PARTICIPATING_ROLES },
    },
    options: {
      perDocumentLimit: 1,
      sort: 'validFrom',
    },
  });
  // Add getter in this way, see: https://github.com/Automattic/mongoose/issues/5835
  // to be fixed in mongoose 6.
  firstJoined.getters.unshift(function firstJoinedGetter() {
    if (!this.firstJoined) return undefined;
    const { validFrom } = this.firstJoined;

    // If the earliest membership is in the future, you haven't joined yet!
    if (validFrom && validFrom > new Date()) return undefined;

    // Okay, use validFrom. If it is null, then treat them as joining when this
    // profile was created for them. If that wasn't fetched, give up.
    return validFrom || this.createdAt || undefined;
  });

This lies somewhere between mongoose-lean-virtuals and mongoose-lean-getters. I chose this lib since this is technically about a getter being executed.

My first stab was to run all the getters on virtual fields, but that was breaking those values being returned that were populated but did not have getters. So I'm trying to detect if there are getters added by the developer by checking the length of virtualType.getters.length > 1. I'm not sure this is totally the right approach. If you give me a pointer or two, I can make changes that you suggest so this can land.

makinde commented 4 years ago

Ah, figured out that this PR (https://github.com/Automattic/mongoose/pull/8775) needs to land to avoid the hacks of only applying when there is more than one getter.

vkarpov15 commented 4 years ago

@makinde I think this belongs in mongoose-lean-virtuals, because Mongoose treats virtuals as a separate concept from getters. Plus it is much easier to make these changes in mongoose-lean-virtuals, see: https://github.com/vkarpov15/mongoose-lean-virtuals/commit/d240f3c1ce404ce7bf87a0c886a47fa38cb50b7b .

With your changes in Automattic/mongoose#8775, I can confirm the below script works:

'use strict';

const mongoose = require('mongoose');

mongoose.set('useFindAndModify', false);

const { Schema } = mongoose;

run().catch(err => console.log(err));

async function run() {
  await mongoose.connect('mongodb://localhost:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });

  await mongoose.connection.dropDatabase();

  const childSchema = Schema({ name: String, parentId: 'ObjectId' });
  const Child = mongoose.model('Child', childSchema);

  const parentSchema = Schema({ name: String });
  parentSchema.virtual('children', {
    ref: 'Child',
    localField: '_id',
    foreignField: 'parentId'
  });
  parentSchema.virtual('children').getters.unshift(function(v) {
    console.log('Children virtual getter called!');
    return v;
  });
  parentSchema.plugin(require('mongoose-lean-virtuals'));
  const Parent = mongoose.model('Parent', parentSchema);

  const p = await Parent.create({ name: 'Darth Vader' });
  const c = await Child.create({ name: 'Luke Skywalker', parentId: p });

  const doc = await Parent.findOne().populate('children').lean({ virtuals: true });
  console.log(doc);
}
makinde commented 4 years ago

Thanks for looking at this. Moving the discussion to the thread on vkarpov15/mongoose-lean-virtuals@d240f3c