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.
Related
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);
I have two models, Post and Comment:
My Post model (models/post.js):
var mongoose = require('mongoose');
var Comment = require('../models/comment');
var Schema = mongoose.Schema;
module.exports = mongoose.model('Post', new Schema({
text: {type: String, trim: true},
postedBy: String,
comments: [Comment]
}));
My Comment model (models/comment.js):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('Comment', new Schema({
user: String,
comment: {type: String, trim: true},
created: {type: Date, default: Date.now(), select: false}
}));
When I attempt to create a new post without any comments, the post is created perfectly fine.
Although when I try to $push a comment to the post after creation, nothing happens.
Post.findOneAndUpdate(
{"_id": req.params.id},
{$push: {comments: {
comment: "Hello World",
user: "933ujrfn393r"
}}
}).exec(function(err, post) {
console.log(post);
res.json({success: true});
});
Why is this failing to push the comment to the post? My console.log(post) line simply logs undefined, so not too sure what is happening here. I tried a simple test of Post.findOne({"_id": req.params.id}) and it returned the post successfully, so there is no problem with the find query.
Embedded sub documents
Your usage implies an embedded sub document inside the model which only requires a schema definition for the sub document. This will store both schema's in a single document in a single collection in MongoDB
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CommentSchema = new Schema({
user: String,
comment: {type: String, trim: true},
created: {type: Date, default: Date.now(), select: false}
});
var PostSchema = new Schema({
text: {type: String, trim: true},
postedBy: String,
comments: [CommentSchema]
});
module.exports = mongoose.model('Post', PostSchema);
Then create comments as you were.
Post.findOneAndUpdate(
{"_id": req.params.id},
{$push: {comments: {
comment: "Hello World",
user: "933ujrfn393r"
}}
}).then(function (post) {
console.log(post);
res.json({success: true});
});
Document references
If you want to keep the two models then you would need to use a reference in your Post schema instead. This will create seperate documents in seperate collections in MongoDB and use the _id to look up the second document.
var PostSchema = new Schema({
text: {type: String, trim: true},
postedBy: String,
comments: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
});
Then comments need to be created before you can reference them in the Post model.
c = new Comment({ comment: 'x' })
c.save().then(function (result) {
return Post.findOneAndUpdate(
{ _id: req.params.id },
{ $push: { comments: result._id } }
);
}).then(function (result) {
console.log('updated post');
});
Population can be used to easily retrieve the "foreign" documents.
Based on this question, I believe your problem is that you're embedding the Comment Model instead of the Comment Schema.
Try changing post.js from:
var Comment = require('../models/comment');
to:
var Comment = require('../models/comment').schema;
This also makes sense after looking at the example on the Mongoose docs regarding sub-docs.
P.S.
What helped me investigate this was outputting the err object of the exec callback to see what was actually going on...
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 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);
})
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