How mongoose populate more than 2 level deep? - node.js

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?

Related

Mongoose references: Should documents cross reference each other

I need some input on schema design with mongodb and mongoose.
I have the following scenario data entities:
Posts
Comments
Users
Requirements:
A user can make a comment on a post.
Comments can be served for a post.
A list of all comments of a user can be retrieved.
I'm thinking to make all 3 of them a seperate schema and connect them by using ref.
I see two approaches here and need input on what might be smarter. Should every entity link to its relations or is it enough if only the comments are a "link" to the other data objects?
export const commentSchema = new Schema<CommentDocument>({
content: { type: String, required: true },
userId: { required: true, type: Schema.Types.ObjectId, ref: "user" },
postId: { type: Schema.Types.ObjectId, ref: "post" },
});
And then for both user and post, should they also link to the comment again or is it enough if the relationship is stored once in comment?
export const userSchema = new Schema<UserDocument>({
// ... all my user data
comments: [{ type: Schema.Types.ObjectId, ref: "comment" }] // <--- Is the referencing on the other documents useful?
});
export const postSchema = new Schema<PostDocument>({
// ... all my post data
comments: [{ type: Schema.Types.ObjectId, ref: "comment" }] // <--- Is the referencing on the other documents useful?
});
Is there any rule of thumb for declaring the references between the documents? Is this generally a good schema design approach?
your commentSchema is good
But no need to store commentsId in userSchema& postSchema.
Because, it will be good for fews comment but for large no of comments it is not a good approach.

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);

Mongoose query populate match id of find elements

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

Using Mongoose to findbyid and update sibling

I'm trying to locate a reference to another schema and update a sibling field. Specifically, I'm trying to manipulate the hasResponded field below based on a particular 'survey' ObjectId.
My schema looks like this:
var userSchema = new mongoose.Schema({
// some other stuff
surveys: [{
survey: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Survey'
},
hasResponded: {
type: Boolean,
default: false
}
}]
});
If you have a survey id, simply search for all the users that have this particular id in the array.
Something like that:
Users.find({surveys: { "$elemMatch": { type: <ID> } } });
Then, iterate through the users and their corresponding survey array to find the ones that match the id you gave.
Got to say I would structure this db a little different if this query takes place often.
Make a new Schema - UserSurveys that holds the id of the user and the survey + hasResponded. Like this:
var UserSurveySchema = new mongoose.Schema({
user_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
survey_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'}
hasResponded: {type:Boolean, 'default':false}
...
});
You might also want to keep an index on the user and survey ids.
Then, it will be much easier to update the field, requests will take much shorter times.
Hope this helps.

Resources