How to populate a mongoose schema - node.js

I have the following mongoose schemas
var postTable = mongoose.Schema({
userPost:{type : String},
dateCreated: {type: Date, default: Date.now},
_replies:[{type: mongoose.Schema.Types.ObjectId, ref: 'reply_table'}],
_creator:{type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
and
var reply_table = mongoose.Schema({
userReply:{type:String},
date_created:{type:Date, default: Date.now},
_post:{type: mongoose.Schema.Types.ObjectId, ref: 'post'},
_creator:{type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
var userPost = module.exports = mongoose.model("Post",postTable);
var userReply = module.exports = mongoose.model('reply_table',reply_table);
User can create post which will be entered into the Post table and other users can comment or reply to a post which will be entered into the reply_table.
I then try to populate the the post table like this
module.exports.getPost = function (callback) {
var mysort = { dateCreated: -1 };
userPost
.find({},callback)
.sort(mysort)
.populate('_creator','username')
.populate(' _replies')
.exec(function (err,post) {
if(err) throw err;
console.log(post)
});
};
When the console prints out the post it prints the post information and a object with the user information becausei have another schema setup for users, therefore I used .populate('_creator','username')
The problem is it wont print the reply information it only prints an empty array: reply[].
I'm pretty sure I'm doing everything right. I used the following code to insert information into the reply_table
//make a reply on a post
module.exports.make_reply = function (user_id,pid,reply,callback) {
var newReply = userReply({
_creator: user_id,
_post: pid,
userReply: reply
});
newReply.save(callback);
}
I know this question is very long but does anyone have any idea of what I might be doing wrong. I only want to populate the Post schema with information from the reply_table

I finally figured out a solution to my question. What i did was i created a function to insert the reply id into the post table. It basically get the comment by its id and push a reply into the _replies array in the post table.
//Insert reply into post table
module.exports.addReply = function (id,reply) {
userPost.update({_id:id},{$push:{replies:reply}},{multi:true},function
(err,post) {
});
}
When i use the getPost function it populates the reply table
module.exports.getPost = function (callback) {
var mysort = {dateCreated: -1};
userPost
.find({}, callback)
.sort(mysort)
.populate('_creator', 'username')
.populate('replies')
.exec(function (err) {
if(err) throw err;
});
};

Related

why isnt findbyidandupdate pushing new comment into array?

I'm trying to add in comments in an array to a main comment. Here i'm getting getting data, using it to create a new comment, and then pushing it into it's parent comment in the children array. But when it findbyidandupdate goes through, the parent comment doesn't have this comment in its children array.
I did a second find below it to see if it returns new updated children array but nothing. I'm inputting the new comment's id into the children array.
my schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PK = mongoose.Types.ObjectId;
var RK = mongoose.Schema.ObjectId;
var CommentSchema = Schema({
body: {type: String},
chapterId: {type: RK, ref: 'Chapter'},
by: {type: RK, ref: 'User'},
children: [{
type: RK,
ref: 'Comment'
}]
}, {timestamps: true});
function autoPopulateComment() {
this.populate('by');
}
CommentSchema.
pre('findOne', autoPopulateComment).
pre('find', autoPopulateComment);
module.exports = mongoose.model('Comment', CommentSchema)
;
my controller:
commentController.reply = function(req,res){
var commentList;
var newComment = new Comment(req.body);
newComment.save();
console.log(newComment);
var commid = req.body.comid;
//console.log(req.body.comid);
//console.log(req.body.body);
//console.log(req.body.xxchid);
//console.log(req.body.by);
Comment.findByIdAndUpdate(
commid,
{$push: {"children": newComment._id}},
{new: true}
)
Comment.find({_id: commid}).then(function(comment){
console.log(comment);
})
Chapter.find({_id: req.xxchid}).then(function(chapter){
Comment.find({chapterId: req.xxchid}).then(function(data){
return res.send({commentList: data, comidd: commid, test1:"testy"})
})
})
}
Changed the update function to this and it worked. Guess it needed a callback shrugs. Anyways thnx stackoverflow! hehexd
Comment.findByIdAndUpdate(
commid,
{$push: {children: newComment}},
{new: true},
function(err, post){
if(err){
console.log(err);
} else{
console.log(post+"haha")
}
}
)

How can I merge results into a single JSON while querying data from two different models in node.js and mongoose?

I have two mongoose models in my app:
var UsersSchema = new Schema({
username: {type: String},
facebook_username: {type: String},
display_name: {type: String}
}
and
var CommentsSchema = new Schema({
user_id: {type: String, required: true},
text_content: {type: String},
photo_content_url: {type: String}
comment_date: {type: Date, default: Date.now},
created_at: {type: Date, default: Date.now},
}
currently each single comment contains user_id - a direct link to its author.
I created a node.js endpoint that takes POST query and returns JSON with all details about comments:
commentsRoutes.post('/comments', function (req, res) {
var startDate = req.body.startDate;
var endDate = req.body.endDate;
var query= {};
query.$and = [];
// and condition on start date
if(startDate != undefined) {
var startDate = new Date(req.param('startDate'));
var endDate = new Date(req.param('endDate'));
query.$and.push({"comment_date":{$gte: startDate}});
query.$and.push({"comment_date":{$lte: endDate}});
}
var finalquery = Comments.find(query)
finalquery.exec(function(err, comments){
if(err) {
res.send(err);
return;
}
res.json(comments);
});
});
This endpoint returns me a JSON with all the comments, however - I need to attach to each JSON details about its author - username, facebook_username and display_name (fetched based on user_id from UsersSchema). Can you tell me how could I do it?
user_id in CommentsSchema is a mongoose id of a specific user from UsersSchema
====== EDIT
Like #Antonio suggested in his answer below, I used populate in my case, it worked well and I saw merged JSON at the end. All user details were added, it looked like:
{
"text_content": "blah",
"photo_content_url": "http://www...",
"comment_date": "some date",
"created_at": "some date",
"user_id": { "username": "someUsername",
"facebook_username": "fbUsername",
"display_name": "sth" }
}
however - is there a way to include POST parameters in my endpoint that will apply to the user_id json?
Currently I'm sending a POST parameters startDate and endDate. But if I send also facebook_username - how can I include it in a query and find only comments that were written by this author?
I tried adding a simple condition:
var fb_username = req.body.facebookUsername;
query.$and.push({"facebook_username": fb_username});
but it didn't work because there's no such field facebook_username in CommentsSchema...
So how can I include condition attached to fields from UsersSchema that will limit results from CommentsSchema?
Since you have a reference to the corresponding user you could use populate.
Taking into account that try this:
Comments
.find({
comment_date: {
$gte: startDate,
$lte: endDate
}
})
.populate('user_id')
.exec(function(err, comments) {
if(err) {
return res.send(err);
}
res.json(comments);
});
By the way, not related to the main question, but since you are not doing any change in the server I think a GET would be a better option.
I also abbreviated your query, $and is not necessary here.
EDIT
You can add filtering to populate, in your case:
.populate({
path: 'user_id',
match: {
facebook_username: fb_username
}
})

mongoose find a document by reference property

I have a first model Person:
var personSchema = new Schema({
firstname: String,
name: String
});
module.exports = mongoose.model('Person', personSchema);
And a second model Couple:
var coupleSchema = new Schema({
person1: [{ type: Schema.Types.ObjectId, ref: 'Person' }],
person2: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
module.exports = mongoose.model('Couple', coupleSchema);
I find a couple with a person ObjectId:
Couple.find({
'person1': req.params.objectid
})
.populate({
path: 'person1 person2'
})
.exec(function (err, couple) {
if (err)
res.send(err);
res.json(couple)
});
But I would like to find a couple by giving a firstname and not an ObjectId of a Person, something like that:
Couple.find({
'person1.firstname': "Bob"
})
.populate({
path: 'person1 person2'
})
.exec(function (err, couple) {
if (err)
res.send(err);
res.json(couple)
});
But it is always empty...
Anyway to solve this?
Thank you for any feedback.
EDIT
I just implemented the answer:
Let's see my Couple model now:
var Person = require('mongoose').model('Person');
var coupleSchema = new Schema({
person1 : [{ type: Schema.Types.ObjectId, ref: 'Person' }],
person2 : [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
coupleSchema.statics.findByUsername = function (username, callback) {
var query = this.findOne()
Person.findOne({'firstname': username}, function (error, person) {
query.where(
{person1: person._id}
).exec(callback);
})
return query
}
module.exports = mongoose.model('Couple', coupleSchema);
With this usage:
Couple.findByUsername(req.params.username, function (err, couple) {
if(err)
res.send(err);
res.json(couple);
});
That works! Thank you for your answer and edits.
In your couple model, person1 is an ObjectID (I know you know it), so it has no obviously no property .firstname.
Actually the best way to achieve this, is to find the user by it's first name, and then query the couple, with the id of the user.
This method could/should stand in the couple model as a static method (simplified code sample):
couple.statics.findByPersonFirstname = function (firstname, callback) {
var query = this.findOne()
Person.findOne({firstname: firstname}, function (error, person) {
query.where($or: [
{person1: person._id},
{person1: person._id}
]).exec(callback);
})
return query
}
Just like this exemple.
EDIT: Also note that the ref must be the _id (so you couldn't store with the first name, that would be a bad idea anyway).
Considering your edit:
Person._id is maybe a String and the reference is an ObjectId, if so, try:
{person1: mongoose.Types.ObjectId(Person._id)}
Also, your variable is person and not Person. Try to log person to see if you get something.
Finally, my code sample is really simple, don't forget to handle errors and all (see the link I gave you above, which is complete).

Nested query with mongoose

I have three models: User, Post and Comment
var User = new Schema({
name: String,
email: String,
password: String // obviously encrypted
});
var Post = new Schema({
title: String,
author: { type: Schema.ObjectId, ref: 'User' }
});
var Comment = new Schema({
text: String,
post: { type: Schema.ObjectId, ref: 'Post' },
author: { type: Schema.ObjectId, ref: 'User' }
});
I need to get all posts in which the user has commented.
I know it should be a very simple and common use case, but right now I can't figure a way to make the query without multiple calls and manually iterating the results.
I've been thinking of adding a comments field to the Post schema (which I'd prefer to avoid) and make something like:
Post.find()
.populate({ path: 'comments', match: { author: user } })
.exec(function (err, posts) {
console.log(posts);
});
Any clues without modifying my original schemas?
Thanks
You have basically a couple of approaches to solving this.
1) Without populating. This uses promises with multiple calls. First query the Comment model for the particular user, then in the callback returned use the post ids in the comments to get the posts. You can use the promises like this:
var promise = Comment.find({ "author": userId }).select("post").exec();
promise.then(function (comments) {
var postIds = comments.map(function (c) {
return c.post;
});
return Post.find({ "_id": { "$in": postIds }).exec();
}).then(function (posts) {
// do something with the posts here
console.log(posts);
}).then(null, function (err) {
// handle error here
});
2) Using populate. Query the Comment model for a particular user using the given userId, select just the post field you want and populate it:
var query = Comment.find({ "author": userId });
query.select("post").populate("post");
query.exec(function(err, results){
console.log(results);
var posts = results.map(function (r) { return r.post; });
console.log(posts);
});

Way to save associations in mongoose without doing a save dance?

Say I have this schema.
var Topic = new Schema({
owner: {
type: Schema.Types.ObjectId,
ref: 'User'
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
title: String,
date: Date,
lastPost: Date,
likes: Number,
posts: [{
type: Schema.Types.ObjectId,
ref: 'Post'
}]
});
var Post = new Schema({
topic: {
type: Schema.Types.ObjectId,
ref: 'Topic'
},
body: String,
date: Date,
owner: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
If I want to save the Topic then add the Topic to the topic association on the Post and then push to the posts array on the Topic object, I have to do this weird dance.
exports.create = function (req, res) {
var data = req.body.data;
var topic = new Topic();
topic.title = data.topic.title;
topic.date = new Date();
topic.lastPost = new Date();
topic.save(function (err, topicNew) {
if (err) res.send(err, 500);
var post = new Post();
post.body = data.post.body;
post.topic = topicNew;
topicNew.posts.push(post);
topic.save(function (err, t) {
if (err) res.send(err, 500);
post.save(function (err, p) {
if (err) res.send(err, 500);
return res.json(t);
});
});
});
};
I'm not seeing anything in the documentation that would help me around this. Thanks for any help.
Instantiate both the topic and the post initially. Push the post into the topic before the first topic save. Then save the topic and if that succeeds save the post. MongoDB object IDs are created by the driver right when you do new Post() so you can save that in the topic.posts array before it's saved.
That will make your 3-step dance a 2-step dance, but in the grand scheme of things this seems essentially par for the course so I wouldn't set my expectations much lower than this. Very few useful real world routes can be implemented with a single DB command.
You can also use middleware functions as a way to get sequential async operations without nesting. You can use the req object to store intermediate results and pass them from one middleware to the next.

Resources