Mongoose: justOne: true option does not work correctly when populating virtuals of array field

Created on 15 Aug 2018  Â·  1Comment  Â·  Source: Automattic/mongoose

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

Bug

What is the current behavior?

  • The bug occurs when using the Populate Virtuals feature.
  • If the virtual field have the justOne: true option set, but the virtual field returns the same first item in the output result.
  • If i set justOne: false it works correctly. But it returns an array instead of single object.

Summary: the detailItem in the second item of array have _id does not same with idItem. Data output looks like:

"items":[
      {
         "value":"1",
         "idItem":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6",
         "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
         "_id":"0a65878c-d2b5-4c47-8de0-61cf65b64057",
         "id":"0a65878c-d2b5-4c47-8de0-61cf65b64057",
         "itemDetail":{
            "_id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6",
            "name":"DV",
            "ghi_chu":"Chi phí dịch vụ",
            "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
            "createdAt":"2018-08-14T17:22:48.415Z",
            "updatedAt":"2018-08-14T17:22:48.415Z",
            "id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6"
         }
      },
      {
         "value":"2",
         "idItem":"fabbb077-5d59-4576-9fd9-97d47f2db12e",
         "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
         "_id":"e2402c16-7d1a-4f11-95fe-76e060c36967",
         "id":"e2402c16-7d1a-4f11-95fe-76e060c36967",
         "itemDetail":{ // here is the problem, output is same with the first item
            "_id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6",  // not same `idItem`
            "code":"",
            "name":"DV",
            "ghi_chu":"Chi phí dịch vụ",
            "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
            "createdAt":"2018-08-14T17:22:48.415Z",
            "updatedAt":"2018-08-14T17:22:48.415Z",
            "id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6"
         }
      }
   ],

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

Here are the steps:

  1. Define mongoose model
var uuid = require("uuid");
var mongoose = require('mongoose');

var Schema = mongoose.Schema;

var ReportItemSchema = new Schema({
    _id: {
        type: String,
        default: uuid.v4
    },
    idItem: {
        type: String,
        ref: 'Item'
    },
    idItemParent: {
        type: String,
        ref: 'Item'
    },
    value: String,
    mediaType: String
}, {
    timestamps: true,
    toJSON: {
        virtuals: true
    },
    toObject: {
        virtuals: true
    }
});

var ReportSchema = new Schema({
    _id: {
        type: String,
        default: uuid.v4
    },
    items: [ReportItemSchema],
    status: {
        type: Number,
        default: 0
    }
}, {
    id: true,
    timestamps: true,
    toJSON: {
        virtuals: true
    },
    toObject: {
        virtuals: true
    }
});

/**
 * Virtual fields definition
 */

ReportItemSchema.virtual('itemDetail', {
    ref: 'Item',
    localField: 'idItem',
    foreignField: '_id',
    justOne: true  // here is the problem
});

ReportItemSchema.virtual('itemParent', {
    ref: 'Item',
    localField: 'idItemParent',
    foreignField: '_id',
    justOne: true
});
// Model Item definition
var ItemSchema = new Schema({
    _id: {
        type: String,
        default: uuid.v4
    },
    code: String,
    name: String,
    ghi_chu: String,
    idItemParent: String
}, {
    id: true,
    timestamps: true,
    toJSON: {
        virtuals: true
    },
    toObject: {
        virtuals: true
    }
});

/**
 * Virtual fields definition
 */
ItemSchema.virtual("parent", {
    ref: 'Item',
    localField: 'idItemParent',
    foreignField: '_id',
    justOne: true
}).set(function (value) {
    if (value) {
        this.idItemParent = value._id;
    }
});

/**
 * Model definition
 */

var ItemModel = mongoose.model("Item", ItemSchema);
var ReportModel = mongoose.model("Report", ReportSchema);
  1. Query data by findOne
    read(req, res) {
        var id = req.param('id');

        this.ReportModel.findOne({
                _id: id
            })
            .populate('items.itemDetail')
            .populate('items.itemParent')
            .then((doc) => {
                res.ok(doc)
            })
            .catch((err) => {
                res.error(err);
            })
    }
  1. Data output have
{
   "status":0,
   "_id":"a6a14b80-8795-4864-9359-0b28b44fb6b9",
   "items":[
      {
         "value":"1",
         "idItem":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6",
         "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
         "_id":"0a65878c-d2b5-4c47-8de0-61cf65b64057",
         "id":"0a65878c-d2b5-4c47-8de0-61cf65b64057",
         "itemDetail":{
            "_id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6",
            "name":"DV",
            "ghi_chu":"Chi phí dịch vụ",
            "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
            "createdAt":"2018-08-14T17:22:48.415Z",
            "updatedAt":"2018-08-14T17:22:48.415Z",
            "__v":0,
            "parent":null,
            "id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6"
         },
         "itemParent":{
            "_id":"38d801d5-20f7-49df-b394-5afdfba6807d",
            "name":"Thống kê chi phí",
            "ghi_chu":"",
            "createdAt":"2018-08-14T10:26:23.878Z",
            "updatedAt":"2018-08-14T10:26:23.878Z",
            "__v":0,
            "parent":null,
            "id":"38d801d5-20f7-49df-b394-5afdfba6807d"
         }
      },
      {
         "value":"2",
         "idItem":"fabbb077-5d59-4576-9fd9-97d47f2db12e",
         "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
         "_id":"e2402c16-7d1a-4f11-95fe-76e060c36967",
         "id":"e2402c16-7d1a-4f11-95fe-76e060c36967",
         "itemDetail":{ // here is the problem, output is same with the first item
            "_id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6",
            "code":"",
            "name":"DV",
            "ghi_chu":"Chi phí dịch vụ",
            "idItemParent":"38d801d5-20f7-49df-b394-5afdfba6807d",
            "createdAt":"2018-08-14T17:22:48.415Z",
            "updatedAt":"2018-08-14T17:22:48.415Z",
            "__v":0,
            "parent":null,
            "id":"b3d379d5-8eb2-4c52-a098-7021bdc66eb6"
         },
         "itemParent":{
            "_id":"38d801d5-20f7-49df-b394-5afdfba6807d",
            "name":"Thống kê chi phí",
            "ghi_chu":"",
            "createdAt":"2018-08-14T10:26:23.878Z",
            "updatedAt":"2018-08-14T10:26:23.878Z",
            "__v":0,
            "parent":null,
            "id":"38d801d5-20f7-49df-b394-5afdfba6807d"
         }
      }
   ],
   "createdAt":"2018-08-14T19:19:31.170Z",
   "updatedAt":"2018-08-15T07:28:47.823Z",
   "__v":0,
   "id":"a6a14b80-8795-4864-9359-0b28b44fb6b9"
}

What is the expected behavior?

Since justOne: true is set for the virtual field itemDetail, the expected behavior is for itemDetail to contain a single object, and have its own value which references to Item.

Please mention your node.js, mongoose and MongoDB version.

node.js: v8.11.1
MongoDB: v3.2.8
mongoose: v5.2.8

confirmed-bug

Most helpful comment

Confirmed, below script demonstrates the issue:

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.set('debug', true);

const GITHUB_ISSUE = `gh6867`;
const connectionString = `mongodb://localhost:27017/${ GITHUB_ISSUE }`;
const { Schema } = mongoose;

run().then(() => console.log('done')).catch(error => console.error(error.stack));

async function run() {
  await mongoose.connect(connectionString);
  await mongoose.connection.dropDatabase();

  const ReportItemSchema = new Schema({
    idItem: {
        type: String,
        ref: 'Item'
    },
    value: String,
    mediaType: String
  });

  const ReportSchema = new Schema({
    _id: String,
    items: [ReportItemSchema],
    status: {
      type: Number,
      default: 0
    }
  });

  ReportItemSchema.virtual('itemDetail', {
    ref: 'Item',
    localField: 'idItem',
    foreignField: '_id',
    justOne: true  // here is the problem
  });

  var ItemSchema = new Schema({
    _id: String,
    code: String,
    name: String,
    ghi_chu: String,
    idItemParent: String
  });

  ItemSchema.virtual("parent", {
    ref: 'Item',
    localField: 'idItemParent',
    foreignField: '_id',
    justOne: true
  });

  var ItemModel = mongoose.model("Item", ItemSchema);
  var ReportModel = mongoose.model("Report", ReportSchema);

  await ItemModel.create({ _id: 'foo' });

  await ReportModel.create({
    _id: 'test',
    items: [{ idItem: 'foo' }, { idItem: 'bar' }]
  });

  const doc = await ReportModel.findOne({}).populate('items.itemDetail');
  console.log(doc.toObject({ virtuals: true }).items);
}

>All comments

Confirmed, below script demonstrates the issue:

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.set('debug', true);

const GITHUB_ISSUE = `gh6867`;
const connectionString = `mongodb://localhost:27017/${ GITHUB_ISSUE }`;
const { Schema } = mongoose;

run().then(() => console.log('done')).catch(error => console.error(error.stack));

async function run() {
  await mongoose.connect(connectionString);
  await mongoose.connection.dropDatabase();

  const ReportItemSchema = new Schema({
    idItem: {
        type: String,
        ref: 'Item'
    },
    value: String,
    mediaType: String
  });

  const ReportSchema = new Schema({
    _id: String,
    items: [ReportItemSchema],
    status: {
      type: Number,
      default: 0
    }
  });

  ReportItemSchema.virtual('itemDetail', {
    ref: 'Item',
    localField: 'idItem',
    foreignField: '_id',
    justOne: true  // here is the problem
  });

  var ItemSchema = new Schema({
    _id: String,
    code: String,
    name: String,
    ghi_chu: String,
    idItemParent: String
  });

  ItemSchema.virtual("parent", {
    ref: 'Item',
    localField: 'idItemParent',
    foreignField: '_id',
    justOne: true
  });

  var ItemModel = mongoose.model("Item", ItemSchema);
  var ReportModel = mongoose.model("Report", ReportSchema);

  await ItemModel.create({ _id: 'foo' });

  await ReportModel.create({
    _id: 'test',
    items: [{ idItem: 'foo' }, { idItem: 'bar' }]
  });

  const doc = await ReportModel.findOne({}).populate('items.itemDetail');
  console.log(doc.toObject({ virtuals: true }).items);
}
Was this page helpful?
0 / 5 - 0 ratings