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

Virtuals not serialized after v8.5 #14771

Closed
2 tasks done
alko89 opened this issue Jul 31, 2024 · 3 comments · Fixed by #14774
Closed
2 tasks done

Virtuals not serialized after v8.5 #14771

alko89 opened this issue Jul 31, 2024 · 3 comments · Fixed by #14774
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Milestone

Comments

@alko89
Copy link

alko89 commented Jul 31, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

8.5.2

Node.js version

v22.5.1

MongoDB server version

7.0.12

Typescript version (if applicable)

5.5.4

Description

I have a nested schema, that contains calculated (virtual) props based on level. After updating from 8.4.5 to 8.5.2, the virtuals are no longer serialized. I'm using Mongoose with @nestjs/mongoose if that matters, it hasn't been updated since.

enum EProduct {
  TOYS = "Toys",
}

const labs: Record<EProduct, Lab> = {
  [EProduct.TOYS]: {
    baseCapacity: 100,
    baseProduction: 10,
  },
}


@Schema({ _id: false })
export class UserLab {
  @Prop({ required: true })
  product: EProduct;

  @Prop({ required: true, default: 1 })
  capacityLevel: number;

  @Prop({ required: true, default: 1 })
  productionLevel: number;

  @Prop({ required: true, default: new Date() })
  collectTime: Date;

  @Prop({
    virtual: true,
    get: function () {
      const lab = labs[this.product];
      return this.capacityLevel * lab.baseCapacity;
    },
  })
  capacity?: number;

  @Prop({
    virtual: true,
    get: function () {
      const lab = labs[this.product];
      return this.productionLevel * lab.baseProduction;
    },
  })
  production?: number;

  @Prop({
    virtual: true,
    get: function () {
      const now = new Date();
      const diff = getUnixTime(now) - getUnixTime(this.collectTime);
      const productionPerSecond = this.production / 3600;
      const produced = Math.floor(productionPerSecond * diff);
      return produced < this.capacity ? produced : this.capacity;
    },
  })
  produced?: number;
}

@Schema({ _id: false })
export class LabPlot {
  @Prop({ required: true })
  plotId: number;

  @Prop({ type: UserLab, required: false })
  lab?: UserLab;
}

@Schema({
  toObject: {
    getters: true,
  },
  toJSON: {
    getters: true,
  },
})
export class User extends Document {
  @Prop({ required: true, unique: true })
  id: number;

  @Prop({ required: true })
  username: string;

  @Prop({ type: [LabPlot], default: [{ plotId: 0 }] })
  labPlots: LabPlot[];
}

v8.4.5

{
    "_id": "66a9f217839a26bca6511464",
    "id": 475680949,
    "username": "alko89",
    "labPlots": [
        {
            "plotId": 0,
            "lab": {
                "product": "Toys",
                "capacityLevel": 1,
                "productionLevel": 1,
                "collectTime": "2024-07-31T08:14:37.829Z",
                "produced": 65,
                "production": 10,
                "capacity": 100
            }
        }
    ],
    "__v": 0
}

v8.5.2

{
    "_id": "66a9f217839a26bca6511464",
    "id": 475680949,
    "username": "alko89",
    "labPlots": [
        {
            "plotId": 0,
            "lab": {
                "product": "Toys",
                "capacityLevel": 1,
                "productionLevel": 1,
                "collectTime": "2024-07-31T08:14:37.829Z"
            }
        }
    ],
    "__v": 0
}

Steps to Reproduce

Update to v8.5.2

Expected Behavior

Virtuals should be serialized.

@vkarpov15 vkarpov15 added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Aug 1, 2024
@vkarpov15 vkarpov15 added this to the 8.5.3 milestone Aug 1, 2024
@vkarpov15
Copy link
Collaborator

Mongoose playground link

Repro script:

'use strict';

const mongoose = require('mongoose');

const userLabSchema = new mongoose.Schema({
  capacityLevel: Number
});

userLabSchema.virtual('capacityLevelCeil').get(function() {
  return Math.ceil(this.capacityLevel);
});

const labPlotSchema = new mongoose.Schema({
  plotId: Number,
  lab: userLabSchema
});

const userSchema = new mongoose.Schema({
  username: String,
  labPlots: [labPlotSchema]
}, { toObject: { virtuals: true } });

const User = mongoose.model('User', userSchema);

const doc = new User({
  username: 'test',
  labPlots: [{
    plotId: 1,
    lab: { capacityLevel: 3.14 }
  }]
});
console.log(doc.toObject().labPlots[0]);

Output:

{
  plotId: 1,
  lab: {
    capacityLevel: 3.14,
    _id: new ObjectId('66abad45f09d38ae5c9bcd30')
  },
  _id: new ObjectId('66abad45f09d38ae5c9bcd2f')
}

@vkarpov15
Copy link
Collaborator

It looks like, in older versions of Mongoose, schema-level virtual default applied to subdocuments, but not in Mongoose 8.5. We'll revert to the old behavior for backwards compatibility. As a workaround, please add the following to your UserLab class:

@Schema({
  toObject: {
    getters: true,
  },
  toJSON: {
    getters: true,
  },
})

With toObject option set on the user lab schema, the repro script works fine:

const userLabSchema = new mongoose.Schema({
  capacityLevel: Number
}, { toObject: { virtuals: true } }); // <-- added `virtuals: true` here

userLabSchema.virtual('capacityLevelCeil').get(function() {
  return Math.ceil(this.capacityLevel);
});

@alko89
Copy link
Author

alko89 commented Aug 2, 2024

Thanks @vkarpov15 🙌 that makes sense, not sure why I didn't try if that would fix the issue.

vkarpov15 added a commit that referenced this issue Aug 7, 2024
fix(document): apply virtuals to subdocuments if parent schema has `virtuals: true` for backwards compatibility
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Projects
None yet
2 participants