nodejs/express mongoose .populate() on schema with refs to itself - node.js

I have this schema for a User:
const UserSchema = new Schema({
...
friends: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}],
sentRequests: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}],
recievedRequests: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}]
}
Then when trying to show all the recievedRequests to a page I use this code:
router.get('/friendRequests', (req, res) => {
User.findOne({
_id: req.user.id
})
.populate('recievedRequests.user')
.then(curUser => {
res.render('friends/friendRequests', {
curUser: curUser
});
})
});
where User is the model and req.user.id is the id of the currently logged in user. However, in the webpage whenever I reference curUser.recievedRequests.user it simply returns the id of the user and not the actual user object. I am assuming it has to do with the nested schema reference since I can't see anything else that would cause this issue. Does anyone know of a way to resolve this?
Thanks.

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.

How to populate an object inside an object in an array?

I am currently working on a project with mongoose, and I have been stuck in a problem with the mongoose populate() method.
The problem is that I cannot populate an object inside an object in an array.
// * I simplified the code below.
// Profile Document
Profile = model('Profile', new Schema({
_user: {
type: Schema.Types.ObjectId,
ref: 'User',
},
posts: [
{
type: Schema.Types.ObjectId,
ref: 'Post',
// Post = {
// _id: Number,
// _poster: {
// type: Schema.Types.ObjectId,
// ref: 'User',
// }
// };
},
],
}));
// app.js
const profile = await Profile.findOne({ _user: userId })
.populate('_user') // it works
.populate('posts') // it works too
.populate('posts._poster'); // it doesn't work
Is there any way to populate a nested object in an array?
It'd be great if you could answer my question. Thank you in advance.
I tried accessing the notation properly to populate *_poster*, however, it didn't still work. The code is below.
const profile = await Profile.findOne({ _user: userId })
.populate('_user')
.populate('posts');
await profile.toObject();
profile.posts.forEach((post) => {
profile.populate('posts.post._poster');
})
I have tested your schema this works great, could you test and let me know
const profile = await Profile.findOne({
_id: '5f108ed7ecc4881a6c35e27b',
})
.populate('_user')
.populate({
path: 'posts',
populate: {
path: '_poster',
model: 'User',
},
})
.exec();
console.log(profile);

mongoose .populate not working

I'm using nodejs/express and mongoose. However, when trying to use .populate(...) on a document it doesn't work. Here is my code:
I have this schema for a User:
const UserSchema = new Schema({
...
friends: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
...
}],
sentRequests: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
...
}],
recievedRequests: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
...
}]
}
mongoose.model('users', UserSchema);
Then when I try to show all the recievedRequests to a page I use this code:
router.get('/friendRequests', (req, res) => {
User.findOne({
_id: req.user.id // where req.user is the currently logged in user
})
.populate('recievedRequests.user')
.then(curUser => {
res.render('friends/friendRequests', {
curUser: curUser
});
})
});
where User is the model and req.user.id is the id of the currently logged in user. However, in the webpage whenever I reference curUser.recievedRequests.user it simply returns the id of the user and not the actual user object. Does anyone know what I might be doing wrong here?
Thanks.
EDIT - Example
For a document:
user1 = {
_id: ObjectId("5ae94b0b2bb9383d4029d64b"),
...
recievedRequests: [
{"_id" : ObjectId("5ae94b5f29c86c4a343d2d0a") }
]
}
and
user2 = {
_id: ObjectId("5ae94b5f29c86c4a343d2d0a"),
...
}
Using my above code on this document:
router.get('/friendRequests', (req, res) => {
User.findOne({
_id: user1.id
})
.populate('recievedRequests.user')
.then(curUser => {
res.render('friends/friendRequests', {
curUser: curUser
});
})
});
results in curUser.recievedRequests = [{ _id : 5ae94b5f29c86c4a343d2d0a }]

Mapping and getting data from two collections in mongoose

I have two collections User and Contact. User will request another user for contact and both userids will be stored in contact collection with status. I want to use the contact and user in many other places, so wrote separate models.
Now I want to get user profile with contact object, the contact object should have the contact user information.
User Schema
var UserSchema = new mongoose.Schema({
name : {
type:String,
default: '',
required:'Please fill name.',
trim: true
},
mobile : {
type:String,
required:'Please fill mobile',
unique:true,
trim:true
},
contacts:[{
type: mongoose.Schema.ObjectId,
ref: 'Contact'
}]
});
Contact Schema
var ContactSchema = new mongoose.Schema({
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
status: {
type:String,
default:'pending'
}
});
Profile API
User
.findById(req.body.user_id)
.populate({
path: 'contacts',
select: 'user',
})
.exec(function(err, user){
if (err) return res.send(err);
res.status(200).json({'message':'Profile found.','user':user});
});
Here the response will be as follows:
{
"message": "Profile found.",
"user":
{
"_id": "563037f3fe2b69e40b05c451",
"mobile": "32435345",
"__v": 1,
"contacts":
[
{
"_id": "56303f04f1b8524f0d03d9a7",
"user": "563037bafe2b69e40b05c44e"
}
],
"name": "Lorem"
}
}
In the contacts array, I need the name and mobile field for the connected user along with id. How can I achieve this? Or is there any other better approach in schema design?
You should try mongoose-deep-populate module.
var UserSchema = new Schema({
name: String,
mobile: String,
contacts: [{type: mongoose.Schema.ObjectId, ref: 'Contact'}]
})
var ContactSchema = new Schema({
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
status: {
type:String,
default:'pending'
}
});
Initialize the module with a mongoose instance:
var deepPopulate = require('mongoose-deep-populate')(mongoose);
UserSchema.plugin(deepPopulate, options /* more on options below */);
Now you can populate:
User
.findById(req.body.user_id)
.deepPopulate(users, 'contacts.user', function (err, _users) {
// _posts is the same instance as posts and provided for convenience
users.forEach(function (user) {
// post.contacts and user.contacts.user are fully populated
});
});

MongoDB Mongoose schema design

I have a schema design question. I have a UserSchema and a PostSchema.
var User = new Schema({
name: String
});
var Post = new Schema({
user: { type: Schema.Types.ObjectId }
});
Also, user is able to follow other users. Post can be liked by other users.
I would like to query User's followers and User's following, with mongoose features such as limit, skip, sort, etc. I also want to query Post that a user likes.
Basically, my only attempt of solving this is to keep double reference in each schema. The schemas become
var User = new Schema({
name: String,
followers: [{ type: Schema.Types.ObjectId, ref: "User" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
var Post = new Schema({
creator: { type: Schema.Types.ObjectId, ref: "User" },
userLikes: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
so, the code that will be used to query
// Find posts that I create
Post.find({creator: myId}, function(err, post) { ... });
// Find posts that I like
Post.find({userLikes: myId}, function(err, post) { ... });
// Find users that I follow
User.find({followers: myId}, function(err, user) { ... });
// Find users that follow me
User.find({following: myId}, function(err, user) { ... });
Is there a way other than doing double reference like this that seems error prone?
Actally, you don't need the double reference. Let's assume you keep the following reference.
var User = new Schema({
name: String,
following: [{ type: Schema.Types.ObjectId, ref: "User" }]
});
You can use .populate() to get the users you're following:
EDIT: added skip/limit options to show example for pagination
User.findById(myId).populate({ path:'following', options: { skip: 20, limit: 10 } }).exec(function(err, user) {
if (err) {
// handle err
}
if (user) {
// user.following[] <-- contains a populated array of users you're following
}
});
And, as you've already mentioned ...
User.find({following: myId}).exec(function(err, users) { ... });
... retrieves the users that are following you.

Resources