I am trying to understand Mongoose's populate function and am so far stumped by available resources and the docs.
Essentially I have a schema arrangement as below, and I am trying to display all the posts referenced to a user on one page rendered with ejs.
var UserSchema = new Schema ({
username: String,
password: String,
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Post"
}
]
});
var PostSchema = new Schema ({
Title: String,
Content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}
});
I have got passport/sessions setup, so I am able to get the id of the active user. Using this id I would like to display all of the user's posts. I have been stuck on this for hours and tried following various tutorials and different approaches, please someone put me out of my misery! How should I approach this?
Try to match the users first with the ids list (activeUserIds) then populate the posts and author fields in fetched records.
await User.find({'_id': {$in: [activeUserIds]}})
.populate({
path: 'posts',
model: 'Post',
populate: ({
path: 'author',
model: 'User'
})
});
Related
const userSchema = new Schema({
name: String,
email: String,
friends: [{ type: ObjectId, ref: 'User' }]
});
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
populate: { path: 'friends' }
});
The above schema populates the entire User fields for friends array, but for the friends array, I need only name to be populated. How do I limit the fields in the nested level in mongoose.
Generally speaking, you would first populate friends as you have done and then add select to simply get the fields that you wanted to extract from the User model.
User.findOne({ name: 'Val' })
.populate({
path: 'friends',
select: ['name', 'fieldName']
})
I have added array of elements to demonstrate that you can select more fields that way, in your case it would just be name.
I have two mongoose schemes one for users and one for posts.
One user can like many posts.
At the end I would like to present in the client side all the posts the user liked in one section which I can do using the populate() method, and in another section the posts the user didn’t like without creating duplicates of the liked posts.
Is there unpopulate() method I can use to get only the unliked posts? If not, what is the best way to approach this?
userScheme =
{
// some other fields…
post: {
type: Schema.Types.ObjectId,
ref: 'Post'
}
}
postScheme =
{
// some other fields…
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
likes: [
{
type: Schema.Types.ObjectId,
ref: 'User'
}
],
}
You can query for all the posts that doesn't have the particular user in their likes array i.e. is not liked the user yet. Reference
Post.find({ likes: { $nin: [user._id] }})
I define the comment model as:
CommentSchema = new mongoose.Schema ({
name: String,
email: String,
website: String,
content: String,
createDate: Date,
updateDate: Date,
targetBlog: {type: mongoose.Schema.Types.ObjectId, ref: 'Blog'},
childrenComment: [{type: mongoose.Schema.Types.ObjectId, ref: 'Comment'}]
});
And when I use populate as:
Comment.find({targetBlog: blog._id}).populate({path: 'childrenComment'}).exec(function(err, comments) {
console.log(comments);
res.render('blog', { blog: blog, comments: comments});
});
I find mongoose only populate one level deep. So how can I do to make it populate more than one level, because the level can be 2 or 3 or more.
You can specify the model manually when populating.
Comment.find({targetBlog: blog._id})
.populate({path: 'childrenComment', model: 'Comment'})
.exec(function(err, comments) {
console.log(comments);
res.render('blog', { blog: blog, comments: comments});
});
Update:
It looks like your code is working without adding the model. So the problem should be somewhere else, not in the populating. This is what you are trying to do, right?
I'm trying to populate a model with data from another model. Those two models looks like this:
var postSchema = mongoose.Schema({
_comments: { type: mongoose.Schema.Types.ObjectId, ref: 'Comment' },
type: String,
body: String,
});
var commentSchema = mongoose.Schema({
id_post: mongoose.Schema.Types.ObjectId,
body: String,
});
I want to find all posts and populate them with comments that have id_post == _id from founded Posts. Something like this:
Post.find({}).populate({
path: '_comments',
select: 'body',
match: { post_id: Post._id }
options: { limit: 5 }
})
.exec(function (err, posts){...});
First of all, There are few problems in the code you wrote.
If each post may have many comments you should implement one-to-many relationship between your schemas, you can do it by surrounding the comment ref with []
var postSchema = mongoose.Schema({
_comments: [ {type: mongoose.Schema.Types.ObjectId, ref: 'Comment'} ] ,
type: String,
body: String,
});
id_post is not just a field of type ObjectId, it should be written like this:
var commentSchema = mongoose.Schema({
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' },
body: String,
});
When saving a new comment make sure you connect it to its post:
var comment = new Comment({
body: "Hello",
post: post._id // assign the _id from the post
});
comment.save(function (err) {
if (err) return handleError(err);
// thats it!
});
Now when you want to find a post and populate its comments you should write something like this:
Post
.find(...)
.populate({
path: '_comments',
select: 'body',
options: { limit: 5 }
})
.exec()
The reason I dropped the match is that match should be used when you want to filter according to a specific field, in your case you can use match to get only comments with type='something'.
populate should work because when you inserted the comment you made the bond to its post.
More information on the right way of using populate can be found here - Mongoose Query Population
Post data should be persisted the following way:
{
body: "some body",
type: "some type",
_comments: [12346789, 234567890, ...]
}
More information about the way the ref will be persisted here - One-to-Many Relationships with Document References
I'm not sure how to populate the sample schema below or if it is even possible. Can a reference be within an object like below? If you can, how would you populate it? E.g. .populate('map_data.location');?
var sampleSchema = new Schema({
name: String,
map_data: [{
location: {type: Schema.Types.ObjectId, ref: 'location'},
count: Number
}]
});
Or will I have to have two separate arrays for location and count like so:
// Locations and counts should act as one object. They should
// Be synced together perfectly. E.g. locations[i] correlates to counts[i]
locations: [{ type: Schema.Types.ObjectId, ref: 'location'}],
counts: [Number]
I feel like the first solution would be the best, but I'm not entirely sure how to get it working within Mongoose.
Thank you very much for any help!
The first solution is possible.
Mongoose currently has limitations (see this ticket here) populating multiple levels of embedded documents, however is very good at understanding nested paths within a single document - what you're after in this case.
Example syntax would be:
YourSchema.find().populate('map_data.location').exec(...)
Other features, such as specifying getters / setters on paths, orderBy and where clauses, etc. also accept a nested paths, like this example from the docs:
personSchema.virtual('name.full').get(function () {
return this.name.first + ' ' + this.name.last;
});
Internally Mongoose splits the string at the dots and sorts everything out for you.
First option is ok, but if someone would have problems with that query map_data.location - Mongoose returns empty array instead of object - I found that this will work:
.populate({
path: 'map_data.location',
model: 'Location'
})
YourSchema.find()
.populate({
path: 'map_data',
populate: {
path: 'location'
}
}).exec(...)
The above statement will populate array of map_data and also the location object of each map_data.
hope this helps someone.
i think you will need this:
if you schema is this:
var sampleSchema = new Schema({
name: String,
map_data: [{
location: {type: Schema.Types.ObjectId, ref: 'location'},
count: Number
}]
});
you monggosse query have to be:
const responseMap=await YourSchema.find()
.populate(
path: 'map_data._id',
model: 'location'
select:['nameLocation','geoPoints','count'],
).exec();
console.log(responseMap);
but you count variable have to be in location schema, and map_data have to containt only id location.
If someone couldn't solve his/her problem I got a solution It works for me.
My Schema:
const categorySchema = new Schema(
{
name: {
type: String,
unique: true
},
products: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
})
And then to get a category with an specific name and with their products:
.get('/:name', async (req, res) => {
const { name } = req.params;
const responseMap = await Category.find({name})
.populate({
path: 'products',
model: 'Product',
})
.exec();
res.send(responseMap);})
The first option is the best. "count" is part of object "map_data".