How do I deal with nested mongoose queries and async issues in my one-to-many relationship? How do I deal with nested mongoose queries and async issues in my one-to-many relationship? mongoose mongoose

How do I deal with nested mongoose queries and async issues in my one-to-many relationship?


I figured out how to accomplish this. I did have to make some changes to my Schemas. Because Mongo does not allow for joins, we can instead use the populate() Mongoose method (http://mongoosejs.com/docs/populate.html).

Keeping this explanation short, I wanted to show the updated Schemas, and then show how I was able to populate across just one level. At the bottom of this post is another example, showing how to populate across multiple levels.

UserSchema:

// Setup dependencies:var mongoose = require('mongoose'),    Schema = mongoose.Schema;// Setup a schema:var UserSchema = new Schema (    {        username: {            type: String,            minlength: [2, 'Username must be at least 2 characters.'],            maxlength: [20, 'Username must be less than 20 characters.'],            required: [true, 'Your username cannot be blank.'],            trim: true,            unique: true, // username must be unique            dropDups: true,        }, // end username field    },    {        timestamps: true,    });// Instantiate our model and export it:module.exports = mongoose.model('User', UserSchema);

PostSchema:

// Setup dependencies:var mongoose = require('mongoose'),    Schema = mongoose.Schema;// Setup a schema:var PostSchema = new Schema (    {        message: {            type: String,            minlength: [2, 'Your post must be at least 2 characters.'],            maxlength: [2000, 'Your post must be less than 2000 characters.'],            required: [true, 'You cannot submit an empty post.'],            trim: true,        }, // end message field        user: {            type: Schema.Types.ObjectId,            ref: 'User'        },        comments: [{            type: Schema.Types.ObjectId,            ref: 'Comment'        }],    },    {        timestamps: true,    });// updates userID based upon current session login info:PostSchema.methods.updateUserID = function(id) {    this.user = id;    this.save();    return true;};// adds comment to post's comments array:PostSchema.methods.addComment = function(commentID) {    this.comments.push(commentID);    this.save();    return true;};// Instantiate our model and export it:module.exports = mongoose.model('Post', PostSchema);

CommentSchema:

// Setup dependencies:var mongoose = require('mongoose'),    Schema = mongoose.Schema;// Setup a schema:var CommentSchema = new Schema (    {        message: {            type: String,            minlength: [2, 'Your comment must be at least 2 characters.'],            maxlength: [2000, 'Your comment must be less than 2000 characters.'],            required: [true, 'You cannot submit an empty comment.'],            trim: true,        }, // end message field        user: {            type: Schema.Types.ObjectId,            ref: 'User'        },        _post: {            type: Schema.Types.ObjectId,            ref: 'Post'        },    },    {        timestamps: true,    });// Assigns user ID to comment when called (uses session info):CommentSchema.methods.updateUserID = function(id) {    this.user = id;    this.save();    return true;};// Assigns post ID to comment when called:CommentSchema.methods.updatePostID = function(id) {    this._post = id;    this.save();    return true;};// Instantiate our model and export it:module.exports = mongoose.model('Comment', CommentSchema);

Solution: Use the Populate Method:

Using the populate() method (http://mongoosejs.com/docs/populate.html) we can start with our Post model and populate the comments field.

Using the instance methods defined in the PostSchema, we push comment ID's into the Post.comments array, which the populate() method below grabs all comment objects and replaces the IDs with the actual comment objects.

var User = require('mongoose').model('User');var Post = require('mongoose').model('Post');var PostComment = require('mongoose').model('Comment');Post.find({})    .populate('comments')  // populates comments objects based on ids in `comments`    .exec()    .then(function(commentsAndPosts) {        console.log(commentsAndPosts);        return res.json(commentsAndPosts);    })    .catch(function(err) {        console.log(err);        return res.json(err);    })

The link provided to Mongoose's documentation has a nice clean example.

Now, on the front end, each post object has a comments array inside, and all comment objects have been populated! Sweet!

Overview:

I was able to store an array of comment IDs inside each Post object (when a new comment is made, the comment._id is pushed into the Post.comments array). Using populate(), we can query into the Comments collection and grab all comment objects associated with the relevant ID. This is great, as after our populate method completes, we can send back our entire array of all posts and comments as one JSON object, and iterate over them on the front-end.

Populating at Multiple Levels:

Let's say in the above scenarios, I wanted to also get the username for both post and comment authors (note: each comment and post object has a user field which stores a User._id.

We can use populate, by nesting some parameters, to reach across multiple levels as follows. This will delivers the same data as the example above (all posts and all comments), but includes the usernames for both comments and posts based on the stored user id:

Post.find({}) // finds all posts    .populate('user') // populates the user (based on ID) and returns user object (entire user object)    .populate({         path: 'comments', // populates all comments based on comment ID's        populate: {  path: 'user' } // populates all 'user" fields for all comments (again, based on user ID)    })    .exec()    .then(function(commentsAndPostsWithUsers) {        console.log(commentsAndPostsWithUsers);        return res.json(commentsAndPostsWithUsers);    })    .catch(function(err) {        console.log(err);    })

In the above example, we start by grabbing all posts, and then grab all user objects for each post, and then all comments, along with each user object for each comment, and bundle it all up!

Iterating Through on Front-End with Angular:

We can iterate through our returned posts using ng-repeat**:

<!-- Repeating Posts --><div ng-repeat="post in commentsAndPostsWithUsers >    <h3 ng-bind="post.user.username"></h3>    <h2 ng-bind="post.createdAt"></h2>    <p ng-bind="post.message"></p>    <!-- Repeating Comments -->    <div ng-repeat="comment in post.comments">        <h4 ng-bind="comment.user.username"></h4>        <h5 ng-bind="comment.createdAt"></h5>        <p ng-bind="comment.message"></p>    </div></div>

** Angular controller not shown in code example above.

We can access the username for posts or comments via, post.user.username or post.user.username, respectively, as the entire user object has been attached (thus why we have to navigate down to get the username). We can then use another ng-repeat to iterate through post.comments, showing all of the comments. Wishing the best and hope this helps someone avoid the confusion I did!