Mongoose query populate match id of find elements - node.js

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

Related

Why .populate returns [model] and not the content of the collection

I'm learning mongoose, and I have the below code where I create an Author and a Course and I reference the Author in a Course model.
const Course = mongoose.model(
"Course",
new mongoose.Schema({
name: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "Author",
},
})
);
const Author = mongoose.model(
"Author",
new mongoose.Schema({
name: String,
bio: String,
website: String,
})
);
Then I try to list all the courses and populate the author prop but I just get author: [model] instead of the contents.
async function listCourses() {
/* Use the populate method to get the whole author,
and not just the id.
The first arg is the path to the given prop
i.e. author: {
type: mongoose.Schema.Types.ObjectId,
ref: "Author",
},
We can populate multiple props ...
*/
const courses = await Course.find()
.populate('author')
// .populate({ path: "author", model: Author })
// .populate("author", "name -_id")
// .populate('category')
.select("name author");
console.log(courses);
}
I tried several ways to get the content. I also tried some solutions from this question, but nothing worked.
This is the log I get:
_doc: {
_id: 632c00981186461909cebb20,
name: 'Node Course',
author: [model]
},
I checked the docs to see if the way I'm trying is deprecated, but they have the same code as here.
So how can I see the content?
Thanks!
After all, it was a "wrong" logging approach. By iterating over the array of courses and accessing the author prop, the content was revealed.
This is the modified log, in the listCourses function;
console.log(courses.map((c) => c._doc.author));
And this is the output:
isNew: false,
errors: undefined,
_doc: {
_id: 632bfd0c7f727d15fcd3f712,
name: 'Mosh',
bio: 'My bio',
website: 'My Website',
__v: 0
},

Mongoose: how to filter an array of objects inside an object

I have a list of posts and each post contains an array of comments, each comment might be private or public and I want to show Admins all private and public comments but normal users I want to show them only public comments.
here is a part of the post and comment Schema:
const PostSchema = new mongoose.Schema({
title: String,
comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
})
const CommentSchema = new mongoose.Schema({
body: String,
type: { type: String, enum: ['public', 'private'] }
})
here is the solution I came with:
Grab the post by id:
const post= await Post.findById(id);
and then filter:
post.comments = post.comments.filter(c => c.type != "private");
return res.json(post)
but I want to do it full mongoose if that's possible.
Update your Comment schema:
const CommentSchema = new mongoose.Schema({
body: String,
public: Boolean,
post: { type: Schema.Types.ObjectId, ref: 'Post' }
})
You can use mongoose's populate() method to extract the comments under a specific post. The match property is where you enter your query.
Post.findById(id)
.populate({ path: 'comments', match: { 'type': 'public' } })
.exec((err, postWithFilteredComments) => {
res.json({ postWithFilteredComments })
})

Mongoose query: populate top 2 comments from Post Schema

I have 3 collections: User, Post and Comment. Posts has multiple comments.
I want grab 50 posts, populate author, populate comments but I want only top 2 most voted comments sorted by date(_id)
const PostSchema = new Schema({
author: {
type: Schema.Types.ObjectId,
ref: 'User'
},
content: String,
comments: [{
type: Schema.Types.ObjectId,
ref: 'Comment'
}]
});
const Post = mongoose.model('Post', PostSchema);
const CommentSchema = new Schema({
author: {
type: Schema.Types.ObjectId,
ref: 'User'
},
content: String,
votes: Number
});
const Comment = mongoose.model('Comment', CommentSchema);
Post
.find({})
.limit(50)
.populate('author')
.populate('comments')
...
and I dont know how to achieve this.
you can use mongoose populate options to customize your population. In this case limit it to 2.
Try this:
Post
.find({})
.limit(50)
.populate('author')
.populate({
path :'comments',
options : {
sort: { votes: -1 },
limit : 2
}
});
In case you want more cusomization, you can use mongoose Query conditions(select, match, model, path etc.) and options together.
Read Mongoose Population documentation for more detailed information.

Mongoose populate ObjectID from multiple possible collections

I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

Query Mongoose Schema. nested array of comments

I have venues, which each have a comments section. Each comment is a Mongoose Comment schema. Each comment has a creator property, which is a User schema. I'm trying to find all comments a specific user has posted. How can I do this?
var VenueSchema = new mongoose.Schema({
comments: [{
type : mongoose.Schema.Types.ObjectId,
ref: 'Comment',
default: []
}]
},
{minimize: false});
var CommentSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}, {minimize: false});
var UserSchema = new mongoose.Schema({
token: String,
venues: [{ //in case we want users to save their favorite venues
type : mongoose.Schema.Types.ObjectId,
ref: 'Venue'
}]
});
I have tried
Venue.find({
"comments.creator": "55f1fa1263877ed0067b78c0"
}, function(err, docs) {
console.log(docs);
res.send(docs);
})
but it returns an empty array. The "55f1fa1263877ed0067b78c0" is a sample creator _id. Thanks in advance!
you cannot search creater by its id inside Venue collection becouse it collects only Comment ID. So in order to search creater by its id you need to change like below:
var VenueSchema = new mongoose.Schema({
comments: [CommentSchema]
},
{minimize: false});
var CommentSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}, {minimize: false});
var UserSchema = new mongoose.Schema({
token: String,
venues: [{ //in case we want users to save their favorite venues
type : mongoose.Schema.Types.ObjectId,
ref: 'Venue'
}]
});
As venueSchema is storing only ref to comments (which would be comments _id), you will not be able to query comment using venue model. Either you have embed comment document into comments array of venue schema.
Or
Just query the comment collection using comment model as below
Comment.find({
"creator": "55f1fa1263877ed0067b78c0"
}, function(err, docs) {
console.log(docs);
res.send(docs);
})

Resources