unconditionalcoder
unconditionalcoder

Reputation: 743

Why isn't my array populating using Mongoose

I have two schemas: Device and Map.

Device:

   var DeviceSchema = mongoose.Schema({
        deviceName:{
            type: String,
            required: true,
            index: true
        },
       roomCapacity: {
            type: Number
        },
        roomImagePath:{
            type: String,
        },
        mapId:{
            type: Schema.Types.ObjectId,
            ref: 'Map',
            required: true
        },
        coords:{
            type: [Number], //[xcoord, ycoord]
            required: true
        },

        createdAt: { type: Date, default: Date.now }
    });

Map

var MapSchema = mongoose.Schema({
    ownerId:{
        type: Schema.Types.ObjectId,
        ref: 'User',
        required: true
    },
    mapName: {
        type: String
    },
    mapImagePath:{
        type: String,
        required: true
    },

    createdAt: { type: Date, default: Date.now },

    devices: [{type: Schema.Types.ObjectId, ref: 'Device'}]
});

As you can see, my Map has an array that references devices and my Device has a mapId field which references the map. Now, I am trying to call .populate() so I can retrieve the map along with the array of it's devices. I am doing:

module.exports.getMapByIdAndPopulate = function(mapId, callback){
    Map.findOne({_id: mapId}).populate('devices').exec(callback);
};

This returns the map but the device array is empty when it shouldn't be. Also, I create devices in my DB like this:

 var device = new Device({ //create the device
        deviceName: req.body.deviceName,
        roomCapacity: req.body.roomCapacity,
        roomImagePath: roomImagePath,
        mapId: req.body.mapId,
        coords: [req.body.xcoord, req.body.ycoord]
    });

How can I retrieve a map with an array of it's devices? Also, I don't know if I a understanding populate correctly but I never insert any devices into the Map array.

Upvotes: 0

Views: 203

Answers (2)

Roger
Roger

Reputation: 609

First of all, and just to be sure you are doing it as it supposed to be, when you use this syntax

var device = new Device({});

You are only creating an object, so you have not saved it into mongo yet. To do so, just

var device = new Device({});
device.save(function(err){
    //if err, wrong query
    //else, everything went ok
});

Remember that .save is asynchronous.

In the other hand as you said, you did not insert any device's id reference into the devices array under map collection. I think you are misunderstanding the populate method. The populate method will look forward all the ids contained on the specified array (in this case) and will populate each element using the referenced collection in your model. So, if you do not have anything in that array, there is nothing for mongo to populate. You have to insert the devices into the map objects manually.

Upvotes: 0

kazenorin
kazenorin

Reputation: 1465

I've created a simple script to run through your schemas and query:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = mongoose.Types.ObjectId;

mongoose.connect("mongodb://localhost/mytestdb");

var db = mongoose.connection;

db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function () {

    // Schemas copied from your code, condensed to reduce clutter
    var DeviceSchema = mongoose.Schema({
        deviceName: {type: String, required: true, index: true},
        roomCapacity: {type: Number},
        roomImagePath: {type: String},
        mapId: {type: Schema.Types.ObjectId, ref: 'Map', required: true},
        coords: {type: [Number], required: true},
        createdAt: {type: Date, default: Date.now}
    });

    // Omitted ownerId for simplicity sake
    var MapSchema = mongoose.Schema({
        mapName: {type: String},
        mapImagePath: {type: String, required: true},
        createdAt: {type: Date, default: Date.now},
        devices: [{type: Schema.Types.ObjectId, ref: 'Device'}]
    });

    var Device = mongoose.model('Device', DeviceSchema);
    var Map = mongoose.model('Map', MapSchema);

    // Add Map
    new Map({
        mapName: "Map 1",
        mapImagePath: "mapImagePath 1",
        createdAt: new Date(),
        devices: []
    }).save().then(function (map1) {

        // Add a Device
        return new Device({
            deviceName: "Device 1",
            roomCapacity: 123,
            roomImagePath: "roomImagePath",
            mapId: map1._id,
            coords: [345, 456]
        }).save().then(function (device1) {

            // Add the new Device back to the map
            map1.devices.push(device1); // Push the whole device, not just the _id, mongoose would handle this for us

            return map1.save();
        })


    }).then(function (saveResult) {
        console.log(saveResult);

        // mapId is now an ObjectId
        var mapId = saveResult._id;

        return Map.findOne({_id: mapId}).populate('devices').exec().then(function (foundMap) {

            console.log(foundMap);

        });

    }).catch(function (err) {
        console.error(err);
    });


});

The console output:

First console.log contains the newly saved Map with the Device's Object ID:

{ __v: 1,
  mapName: 'Map 1',
  mapImagePath: 'mapImagePath 1',
  _id: 5771e0e5739d78441cff5424,
  devices: [ 5771e0e5739d78441cff5425 ],
  createdAt: Tue Jun 28 2016 02:28:53 GMT+0000 }

Second console.log is the populated .findOne():

{ _id: 5771e0e5739d78441cff5424,
  mapName: 'Map 1',
  mapImagePath: 'mapImagePath 1',
  __v: 1,
  devices:
   [ { _id: 5771e0e5739d78441cff5425,
       deviceName: 'Device 1',
       roomCapacity: 123,
       roomImagePath: 'roomImagePath',
       mapId: 5771e0e5739d78441cff5424,
       __v: 0,
       createdAt: Tue Jun 28 2016 02:28:53 GMT+0000,
       coords: [Object] } ],
  createdAt: Tue Jun 28 2016 02:28:53 GMT+0000 }

Database records show that the data is as expected - the Device stored the Map's ObjectId, the Map stored the Device's ObjectID in an array at the "devices" path.

This shows that the record is correctly populated, implying that your schema and query should be correct.

Therefore we should find out why population doesn't work on your side.

I suggest you to first check the data inside your database, verify that the "devices" array of the maps collection contains ObjectId but not strings. Also make sure that the collections are correctly named and mapped (if not using default name)

Here are the test data of my database:

maps collection:

{ 
    "_id" : ObjectId("5771e0e5739d78441cff5424"), 
    "mapName" : "Map 1", 
    "mapImagePath" : "mapImagePath 1", 
    "devices" : [
        ObjectId("5771e0e5739d78441cff5425")
    ], 
    "createdAt" : ISODate("2016-06-28T02:28:53.091+0000"), 
    "__v" : NumberInt(1)
}

devices collection:

{ 
    "_id" : ObjectId("5771e0e5739d78441cff5425"), 
    "deviceName" : "Device 1", 
    "roomCapacity" : NumberInt(123), 
    "roomImagePath" : "roomImagePath", 
    "mapId" : ObjectId("5771e0e5739d78441cff5424"), 
    "createdAt" : ISODate("2016-06-28T02:28:53.181+0000"), 
    "coords" : [
        NumberInt(345), 
        NumberInt(456)
    ], 
    "__v" : NumberInt(0)
}

Upvotes: 2

Related Questions