Mongoose Populate not working - node.js

Hello i have this Schema(called schema.js):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var RoomSchema = new Schema({
name: { type: String, required: true, index: { unique: true } },
people: { type: Number, required: true },
childrens: {type: Number, required: true},
total: {type: Number, required: true}
});
var Room = mongoose.model('Room', RoomSchema);
var AvSchema = new Schema({
roomId: {type: Schema.Types.ObjectId, ref: 'Room'},
people: { type: Number, required: true },
childrens: {type: Number, required: true},
total: {type: Number, required: true}
});
var Av = mongoose.model('Av', AvSchema);
module.exports = {
Room: Room,
Av: Av
};
in my Route file :
module.exports = function(app) {
var model = require('../models/Schema');
app.get('/api/rooms', function(req, res) {
model.Room.find(function(err, rooms) {
if (err)
res.send(err);
res.json(rooms);
});
});
app.get('/api/av', function(req, res) {
model.Av.find().populate('roomId').exec(function(err, av) {
if (err)
res.send(err);
res.json(av);
});
});
};
A pic of the db :
GET /api/rooms - response:
[{
"_id": "5444d0dd9a31437167eea816",
"name": "Single",
"people": 1,
"childrens": 1,
"total": 4
}, {
"_id": "5444d1009a31437167eea817",
"name": "Double",
"people": 2,
"childrens": 2,
"total": 10
}]
When i call api/rooms looks fine but when i call api/av i got an empty array [] .... Any idea what i do wrong? I should mention that i have inserted records in av collection for both roomsID
Thank you in advance.

By default, Mongoose pluralizes the model name to come up with the name of the collection, so Mongoose is looking in the avs collection instead of av.
You can explicitly set the collection name by passing that as the third parameter to model:
var Av = mongoose.model('Av', AvSchema, 'av');

I had the same issue but none of the answers worked for me.
I wanted to populate a document after it was queried.
This didn't work:
// IIFE for async/await
( async() => {
var user = await User.findOne( { _id } );
await user.populate( 'comments' ); // Doesn't work
} );
The Mongoose Documentation explains that when calling .populate() without a callback it won't be executed. Instead you need to use .populate().execPopulate():
// IIFE for async/await
( async() => {
var user = await User.findOne( { _id } );
await user.populate( 'comments' ).execPopulate(); // Works as expected
} );

Similar to CodyBugstein's answer, I'm posting why it wasn't working in my case, even though it's not the same case as OP's.
I was trying to populate the "pro" field of my schema in a .post('save') hook, as so:
mySchema.post('save', function(doc, next) {
console.log(doc.pro); // Expected to log ObjectID
doc.populate("pro"); // Populate field
console.log(doc.pro); // Expected to log actual pro document
}
However, the 2nd console.log was also logging the ObjectID instead of the doc.
After struggling with this for a solid hour and trying different approaches, I found out that all I had to do was use promises and call execPopulate() so that it returned a fully-fledged promise. I used async/await but you could use .then too:
mySchema.post('save', async function(doc, next) {
console.log(doc.pro); // Expected to log ObjectID
await doc.populate("pro").execPopulate(); // Populate field
console.log(doc.pro); // Expected to log actual pro document
}
This way, the 2nd console.log did log the entire pro doc as expected :)

Since this is the most popular result for the query
mongoose populate not working
I'll include the reason it wasn't working for me, even though it's not a direct answer to this already solved question, in the hopes it will help someone
The problem for me was that I had specified fields in select({..} but not the field I was trying to populate.

Don't forget to add the ref property to the schema for the property you are trying to populate. E.g.
// ...
const orderSchema = new mongoose.Schema({
userId: {
type: Types.ObjectId,
required: true
},
reservationId: {
type: Types.ObjectId,
required: true,
ref: 'Reservation' // <-- don't forget the ref
}
}, {
timestamps: true
})
// ...
See Mongoose Populate

Also check that your schema doesn't have depopulate in toJSON or toObject option set to true. (facepalm myself)
See all schema options

For me, it is was due to incorrect data. The Ids which I want to populate got deleted from the main table.
So when I do populate it didn't filled the populated data because the Ids were not in the table.

add this in your model
const productSchema = mongoose.Schema(
{
name: {
type: String,
required: [true, "product name must be provide"],
minlength: [3, "Name length minimum is 3"],
maxlength: [50, "Name length maximum is 50"],
trim: true,
},
price: {
type: Number,
required: [true, "price must be provide for product"],
default: 0,
},
description: {
type: String,
required: [true, "product description is required"],
trim: true,
},
{
timestamps: true,
toJSON: {
virtuals: true,
},
toObject: {
virtuals: true,
},
}
);
Example:
productSchema.virtual("reviews", {
ref: "Review",
localField: "_id",
foreignField: "product",
justOne: false,
});
How to use:
products = await productModel.find({}).populate("reviews");

For me, the issue was that I did not require the model to be populated at the beginning of the file.

Related

MongoDB populate returning null

I am trying to populate my user schema with items but for some reason it does not populate anything in to the user schema. Could someone please take a look. I have 1 user and 1 item belonging to that user within my database but nothing is populating and I keep seeing null.
User Schema
var mongoose = require('mongoose')
var userSchema = mongoose.Schema({
name: {
type: String,
required: true
},
discordID: {
type: String,
required: true
},
discordImage: {
type: String,
required: true
},
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Item'
}]
})
const User = module.exports = mongoose.model('User', userSchema)
Item Schema
var mongoose = require("mongoose")
var itemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
purchasedPrice: {
type: Number,
required: true
},
purchasedDate: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User"
}
})
const Item = module.exports = mongoose.model("Item", itemSchema)
Populate Code
app.get("/inventory", async (req, res) => {
try {
await req.user.populate({
path: 'items'
}).execPopulate()
console.log(req.user)
} catch (error) {
console.log(error)
}
res.status(200).render("inventory.ejs", { currentUser: req.user })
})
Objects in the DB:
Item:
User:
Used virtual method on user schema to create association
userSchema.virtual("items", {
ref: "Item",
localField: "_id",
foreignField: "author"
})
Worked fine with original code
I keep seeing null.
and
no its just empty
hints there are no items added to your user. You need to have some ids you can populate.
All populate does is convert an ObjectID into a document. There is no magic that will sync itemSchema.author with userSchema.items.
Hence, it's not enough to add the author to the item. You also need to add the item to the author.
So for example, you could add an item like this:
const item = new Item({author: user._id});
await item.save();
req.user.items.push( item );
await req.user.save();
Now when you log req.user, are there any items there?
Once you see objectIds, then you can go back and add that .populate('items') into the mix and I promise you it'll work.

Populating array of collection which contains references to another collections returns empty array

I have two models Vote and Link,I am trying to populate the votes array in link model,The votes array contains id's that references to the collection Vote,which only contains two fields link and User which also refs to same link model mentioned below and a user model respectively
link Schema:-
const linkSchema = new mongoose.Schema(
{
description: {
type: String,
trim: true,
},
url: {
type: String,
trim: true,
},
postedBy: {
type: mongoose.Types.ObjectId,
ref: "User",
},
votes: [{ type: mongoose.Types.ObjectId, ref: "Vote" }],
},
{
timestamps: true,
}
);
linkSchema.index({ description: "text" });
linkSchema.index({ createdAt: -1 });
module.exports = mongoose.model("Link", linkSchema);
Vote schema:-
const mongoose = require("mongoose");
const voteSchema = new mongoose.Schema({
link: { type: mongoose.Types.ObjectId, ref: "Link" },
user: { type: mongoose.Types.ObjectId, ref: "User" },
});
module.exports = mongoose.model("Vote", voteSchema);
but when i try to get the votes of a link,it always return an empty array ,My function:-
const votes = async ({ id }) => {
const linkData = await Link.findById(id).populate("votes").exec();
console.log(linkData);
};
Output Data:-
{
votes: [], //empty always
_id: 5ecb21059a157117c03d4fac,
url: 'https://www.apollographql.com/docs/react/',
description: 'The best GraphQL client for React',
postedBy: 5ec92a58bf38c32b38400705,
createdAt: 2020-05-25T01:36:05.892Z,
updatedAt: 2020-05-25T01:37:52.266Z,
__v: 0
}
Instead of populate(), you can use aggregate() to get your desired output. This should probably work in your case:
Link.aggregate([
{
$match: {
_id: { $in: [mongoose.Types.ObjectId(id)] // as suggested by the questioner
}
},
{
$lookup: {
from: "vote", // collection to join
localField: "votes", // field from the input documents (filtered after _id is matched)
foreignField: "link", // field to compare with, from other collection
as: "linkData" // output array name
}
}
])
Let me know in the comments.

UnhandledPromiseRejectionWarning: TypeError: place.toObject is not a function

Here I am trying to fetch Users Created places using userId. Here are User model and places model and in Controller, I have writing logic to fetch places by userId. Unfortunately, I am getting error "UnhandledPromiseRejectionWarning: TypeError: place.toObject is not a function" during sending response in res.json({ }) method.
Place Model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const placeSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
image: { type: String, required: true },
address: { type: String, required: true },
location: {
lat: { type: Number, required: true },
lng: { type: Number, required: true },
},
creator: { type: mongoose.Types.ObjectId, required: true, ref: 'User'}
});
module.exports = mongoose.model('placemodels', placeSchema);
User Model
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true, minlength: 6 },
image: { type: String, required: true },
places: [{ type: mongoose.Types.ObjectId, required: true, ref: 'Place'}]
});
userSchema.plugin(uniqueValidator);
module.exports = mongoose.model('usermodels', userSchema);
Controller
const getPlacesByUserId = async (req, res, next) => {
const userId = req.params.uid;
let userWithPlaces;
try {
userWithPlaces = await User.findById(userId).populate('placemodels');
} catch (err) {
console.log(err);
const error = new HttpError(
'Fetching places failed, please try again later',
500
);
return next(error);
}
// if (!places || places.length === 0) {
if (!userWithPlaces || userWithPlaces.places.length === 0) {
return next(
new HttpError('Could not find places for the provided user id.', 404)
);
}
res.json({
places: userWithPlaces.places.map(place =>
place.toObject({ getters: true })
)
});
};
The references are really important in mongoose populate. In the schema, the refs refer to the mongoose name of the schema. Since the names are: 'placemodels' and 'usermodels'. The refs fields should use the exact name.
Reference: https://mongoosejs.com/docs/api.html#schematype_SchemaType-ref
The second important part is the parameters of the populate methods. The documentation specifies that the first argument of the populate function is a name path and is an object or a string. In the case above a string is used. It should refer to the name field to populate.
This means that the code should be the following because we want to populate the places field. The schema is responsible to know from where to get the information
...
userWithPlaces = await User.findById(userId).populate('places');
...
References: https://mongoosejs.com/docs/api.html#query_Query-populate
The references are really important in mongoose populate. In the schema, the refs refer to the mongoose name of the schema. Since the names are: 'placemodels' and 'usermodels'. The refs fields should use the exact name.
Reference: https://mongoosejs.com/docs/api.html#schematype_SchemaType-ref
The second important part is the parameters of the populate methods. The documentation specifies that the first argument of the populate function is a name path and is an object or a string. In the case above a string is used. It should refer to the name field to populate.
This means that the code should be the following because we want to populate the places field. The schema is responsible to know from where to get the information

Save array of ObjectId in Schema

I have a model called Shop whos schema looks like this:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
description: String,
stock: { type: Number, default: 100 },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
image: String,
link: String,
tags: [{ type: Schema.ObjectId, ref: 'Tag' }],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Shop', ShopSchema);
I want to use the array tags to reference to another model via ObjectId obviously. This set up works fine when I add ids into the property via db.shops.update({...}, {$set: {tags: ...}}) and the ids get set properly. But when I try to do it via the Express.js controller assigned to the model, nothing gets updated and there even is no error message. Here is update function in the controller:
// Updates an existing shop in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Shop.findById(req.params.id, function (err, shop) {
if (err) { return handleError(res, err); }
if(!shop) { return res.send(404); }
var updated = _.merge(shop, req.body);
shop.updatedAt = new Date();
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, shop);
});
});
};
This works perfect for any other properties of the Shop model but just not for the tags. I also tried to set the type of the tags to string, but that didn't help.
I guess I am missing something about saving arrays in Mongoose?
It looks like the issue is _.merge() cannot handle merging arrays properly, which is the tags array in your case. A workaround would be adding explicit assignment of tags array after the merge, if it is ok to overwrite the existing tags.
var updated = _.merge(shop, req.body);
if (req.body.tags) {
updated.tags = req.body.tags;
}
Hope this helps.. If the workaround is not sufficient you may visit lodash forums.

Mongoose Relationship Populate Doesn't Return results

var SecuritySchema = new Mongoose.Schema({
_bids: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'BuyOrder'
}],
_asks: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'SellOrder'
}]
});
var OrdersSchema = new Mongoose.Schema({
_security: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Security'
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
required: true
}
});
// declare seat covers here too
var models = {
Security: Mongoose.model('Security', SecuritySchema),
BuyOrder: Mongoose.model('BuyOrder', OrdersSchema),
SellOrder: Mongoose.model('SellOrder', OrdersSchema)
};
return models;
And than when I save a new BuyOrder for example:
// I put the 'id' of the security: order.__security = security._id on the client-side
var order = new models.BuyOrder(req.body.order);
order.save(function(err) {
if (err) return console.log(err);
});
And attempt to re-retrieve the associated security:
models.Security.findById(req.params.id).populate({
path: '_bids'
}).exec(function(err, security) {
// the '_bids' array is empty.
});
I think this is some sort of naming issue, but I'm not sure, I've seen examples here and on the moongoose website that use Number as the Id type: http://mongoosejs.com/docs/populate.html
The ref field should use the singular model name
Also, just do:
models.Security.findById(req.params.id).populate('_bids').exec(...
My main suspicion given your snippet at the moment is your req.body.order has _security as a string instead of an array containing a string.
Also, you don't need an id property. Mongodb itself will automatically do the _id as a real BSON ObjectId, and mongoose will add id as a string representation of the same value, so don't worry about that.
While I don't understand your schema (and the circular nature of it?), this code works:
var order = new models.BuyOrder({ price: 100, quantity: 5});
order.save(function(err, orderDoc) {
var security = new models.Security();
security._bids.push(orderDoc);
security.save(function(err, doc) {
models.Security.findById({ _id: doc._id })
.populate("_bids").exec(function(err, security) {
console.log(security);
});
});
});
It:
creates a BuyOrder
saves it
creates a Security
adds to the array of _bids the new orderDoc's _id
saves it
searches for the match and populates
Note that there's not an automatic method for adding the document to the array of _bids, so I've done that manually.
Results:
{ _id: 5224e73af7c90a2017000002,
__v: 0,
_asks: [],
_bids: [ { price: 100,
quantity: 5,
_id: 5224e72ef7c90a2017000001, __v: 0 } ] }

Resources