0

I have assignments schema as shown below:

const mongoose = require('mongoose');

const assignmentSchema = mongoose.Schema({
    name: String,
    document: String,
    startDate: Date,
    endDate: Date,
    trainingId: {type: mongoose.Schema.Types.ObjectId, ref: 'Training'},
    studentId: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}]
});

module.exports = mongoose.model('Assignment', assignmentSchema);

And users schema as shown below:

const mongoose = require('mongoose');

const userSchema = mongoose.Schema({
    firstName: String,
    lastName: String,
    phone: String
});

module.exports = mongoose.model('User', userSchema); 

And am trying to populate the user details based on the ids present in studentId Array

const User = require('../models/user');
exports.assignments_get_unwindstud_based = (req, res, next) => {
  Assignment.aggregate(
    [
      {
        $unwind: "$studentId"
      },
      {
        "$lookup": {
          "from": User.collection.name,
          "localField": "studentId",
          "foreignField": "_id",
          "as": "studentDetails"
        }
      }
    ], function (err, result) {
        if (err) {
        console.log(err);
        return;
        }
        res.json(result); 
    });
};

But, the resultant studentDetails array is showing blank instead of populating and its creating multiple objects for each training

[
    {
        "_id": "5ad4b1394935de1d5064a8bd",
        "studentId": "5ad58e4e151d1b10b850cc16",
        "name": "assignment1",
        "trainingId": "5ad59b231844ba1d60c2c976",
        "studentDetails": []
    },
    {
        "_id": "5ad4b1394935de1d5064a8bd",
        "studentId": "5ad58fed7c0d6614d80ffda9",
        "name": "assignment1",
        "trainingId": "5ad59b231844ba1d60c2c976",
        "studentDetails": []
    }
]

Expected output:

[
    {
        "_id": "5ad4b1394935de1d5064a8bd",
        "studentId": ["5ad58e4e151d1b10b850cc16"],
        "name": "assignment1",
        "trainingId": "5ad59b231844ba1d60c2c976",
        "studentDetails": [
          { ...Student details },
          { ...Student details }
        ]
    }
]

enter image description here enter image description here Where am i doing wrong?

VIK6Galado
  • 650
  • 14
  • 32
  • 1
    That's `"from": "users"` in lowercase, and plural. MongoDB does not know the name of the "Model" in mongoose, and needs the actual collection name on the server. Alternately you can use `"from": User.collection.name` and read the assigned collection name from the model. See [Querying after populate in Mongoose](https://stackoverflow.com/a/44688386/2313887) for a usage example – Neil Lunn Apr 18 '18 at 06:57
  • my collection name is `users` in db and `"from":"users"` is no working – VIK6Galado Apr 18 '18 at 07:09
  • even `from: User.collection.name` is not working – VIK6Galado Apr 18 '18 at 07:14
  • did you type it in exactly like that or did you put quotes `""` around it? Note in the comment and the linked example where the quotes actually are. Also if you're going to read the property from the mode, make sure you actually import it in the local scope, or you'll get a runtime error. – Neil Lunn Apr 18 '18 at 07:16
  • i updated the question.. i have written exactly the same way – VIK6Galado Apr 18 '18 at 07:24
  • 1
    Show the document as it appears in the collection from `"assingments"` and also the expected match from `"users"`. The only two possible reasons are 1. Pointing at the wrong collection (checked) 2. `ObjectId` values do not match, or are possibly "strings" in one of the collections. Probably best to view the documents from the `mongo` shell as opposed to logging the response from node. We should be able to clearly see the values and type of the data. – Neil Lunn Apr 18 '18 at 07:27
  • Thanks actually the `student` `ids` were deleted by mistake..now the details are populating. but its creating 2 objects for same `training` – VIK6Galado Apr 18 '18 at 08:30
  • 1
    Of course it is. You did `$unwind` first. You should not need to do that in modern MongoDB releases. Remove the `$unwind` from the start of the pipeline. – Neil Lunn Apr 18 '18 at 08:32
  • I removed but the `studentDetails` not populating.. my Mongo version `3.6` – VIK6Galado Apr 18 '18 at 08:45
  • Already asked you to show the documents. You still have not done that. Like I said, there are only two possible reasons aside from coding errors, so you really do need to show the detail. It does work, and many people do this without issue. – Neil Lunn Apr 18 '18 at 08:53
  • updated the question have a look. is this the document you are asking about? – VIK6Galado Apr 18 '18 at 09:20
  • Is there something in the provided answer that you believe does not address your question? If so then please comment on the answer to clarify what exactly needs to be addressed that has not. If it does in fact answer the question you asked then please note to [Accept your Answers](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work) to the questions you ask – Neil Lunn May 12 '18 at 08:00

1 Answers1

1

Really not clear what your issue here is exactly, so it's probably best to just demonstrate with a self contained listing. You really should not need to change anything in the provided code other than possibly the uri for the host and database namespace and maybe authentication credentials.

You should choose a database namespace that is different from what your application is using, as the self contained test here is meant to be destructive of any existing data.

Modern nodejs releases past 7.x:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/schooling';

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const userSchema = new Schema({
  firstName: String,
  lastName: String,
  email: String,
});

const assignmentSchema = new Schema({
  studentIds: [{ type: Schema.Types.ObjectId, ref: 'User' }],
  name: String,
  startDate: { type: Date, default: Date.now }
});

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

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {
  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    let users = await User.insertMany(
      ["aaa", "bbb"].map(el => ({ firstName: el, lastName: el, email: `${el}@example.com` }) )
    );

    log(users);

    await Assignment.create({
      studentIds: users,
      name: 'test 1'
    });

    let results = await Assignment.aggregate([
      { "$lookup": {
        "from": User.collection.name,
        "localField": "studentIds",
        "foreignField": "_id",
        "as": "studentDetails"
      }}
    ]);

    log(results);

    mongoose.disconnect();

  } catch(e) {
    console.error(e);
  } finally {
    process.exit();
  }
})();

Or back a few versions:

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

const uri = 'mongodb://localhost/schooling';

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const userSchema = new Schema({
  firstName: String,
  lastName: String,
  email: String,
});

const assignmentSchema = new Schema({
  studentIds: [{ type: Schema.Types.ObjectId, ref: 'User' }],
  name: String,
  startDate: { type: Date, default: Date.now }
});

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

const log = data => console.log(JSON.stringify(data, undefined, 2));

mongoose.connect(uri)
  .then(conn =>   Promise.all(Object.keys(conn.models).map(k => conn.models[k].remove())))
  .then(() =>User.insertMany(
    ["aaa", "bbb"].map(el => ({ firstName: el, lastName: el, email: `${el}@example.com` }) )
  ))
  .then(users => {
    log(users);
    return Assignment.create({
      studentIds: users,
      name: 'test 1'
    })
  })
  .then(() => Assignment.aggregate([
    { "$lookup": {
      "from": User.collection.name,
      "localField": "studentIds",
      "foreignField": "_id",
      "as": "studentDetails"
    }}
  ]))
  .then( results => log(results) )
  .then(() => mongoose.disconnect() )
  .catch( e => console.error(e) )
  .then(() => process.exit());

You should set that up in it's own project folder with no other dependencies and essentially just npm install based on the following package.json being present in that folder along with the listing:

{
  "name": "schooling",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mongoose": "^5.0.15"
  }
}

So that's really just mongoose and it's direct dependencies installed and nothing else.

Either one should give you output along the lines of this:

Mongoose: users.remove({}, {})
Mongoose: assignments.remove({}, {})
Mongoose: users.insertMany([ { _id: 5ad71e821d7f7b05c0316bb0, firstName: 'aaa', lastName: 'aaa', email: 'aaa@example.com', __v: 0 }, { _id: 5ad71e821d7f7b05c0316bb1, firstName: 'bbb', lastName: 'bbb', email: 'bbb@example.com', __v: 0 } ], {})
[
  {
    "_id": "5ad71e821d7f7b05c0316bb0",
    "firstName": "aaa",
    "lastName": "aaa",
    "email": "aaa@example.com",
    "__v": 0
  },
  {
    "_id": "5ad71e821d7f7b05c0316bb1",
    "firstName": "bbb",
    "lastName": "bbb",
    "email": "bbb@example.com",
    "__v": 0
  }
]
Mongoose: assignments.insert({ studentIds: [ ObjectId("5ad71e821d7f7b05c0316bb0"), ObjectId("5ad71e821d7f7b05c0316bb1") ], _id: ObjectId("5ad71e821d7f7b05c0316bb2"), name: 'test 1', startDate: new Date("Wed, 18 Apr 2018 10:31:30 GMT"), __v: 0 })
Mongoose: assignments.aggregate([ { '$lookup': { from: 'users', localField: 'studentIds', foreignField: '_id', as: 'studentDetails'
} } ], {})
[
  {
    "_id": "5ad71e821d7f7b05c0316bb2",
    "studentIds": [
      "5ad71e821d7f7b05c0316bb0",
      "5ad71e821d7f7b05c0316bb1"
    ],
    "name": "test 1",
    "startDate": "2018-04-18T10:31:30.839Z",
    "__v": 0,
    "studentDetails": [
      {
        "_id": "5ad71e821d7f7b05c0316bb0",
        "firstName": "aaa",
        "lastName": "aaa",
        "email": "aaa@example.com",
        "__v": 0
      },
      {
        "_id": "5ad71e821d7f7b05c0316bb1",
        "firstName": "bbb",
        "lastName": "bbb",
        "email": "bbb@example.com",
        "__v": 0
      }
    ]
  }
]

That clearly shows you the items from the "users" collection are being returned into the array target by the $lookup.

If nothing else, this should provide you with a basis to diagnose what is going wrong in your current environment as you can see how things are being done by comparison.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317