TylerMayfield TylerMayfield - 3 months ago 54
Node.js Question

Finding average from referenced Mongoose Schema

Basically I am trying to find the average rating out of 5 based on comments for specific movie titles in a small app using Node.js and Mongoose.

Movie Schema:

var mongoose = require("mongoose");

//SCHEMA SET UP
var movieSchema = new mongoose.Schema({
title: String,
director: String,
year: Number,
comments:[
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]


});

module.exports = mongoose.model("Movie", movieSchema);


Comment Schema:

var mongoose = require("mongoose");

var commentSchema = mongoose.Schema({
author: String,
text: String,
rating: Number,
movie: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Movie"
}
]
});

module.exports = mongoose.model("Comment", commentSchema);


Trying to find the average movie rating:

Comment.aggregate([
{
$group: {
_id: '$movie',
ratingAvg: {$avg: '$rating'}
}
}
], function(err, results){
if(err){
console.log(err);
}else{
console.log(results);
}
}

);


Based on 3 sample ratings(5, 5, 4) on two separate movies this will return:
[ { _id: 'movie', ratingAvg: 4.666666666666667 } ]

So it is getting me the average of ALL the comments, but I want the average depending on the title of the movie. So for example I have a movie with the title of "Big Trouble in Little China" and it has two comments with the ratings of 5 and 4. I want it to return an average of 4.5. Sorry if this question is confusing, I'm super new to MongoDB and Mongoose and I think its a little over my head I'm afraid :(.

Edit: Here are some sample documents from the movie collection:

{ "_id" : ObjectId("57b135aefaa8fcff66b94e3e"), "title" : "Star Trek", "director" : "Idu No", "year" : 2008, "comments" : [ ObjectId("57b
135b9faa8fcff66b94e3f"), ObjectId("57b135c5faa8fcff66b94e40") ], "__v" : 2 }

{ "_id" : ObjectId("57b137b0a64ba6296d0df2d0"), "title" : "Buntley", "director" : "Lucy Mayfield", "year" : 2016, "comments" : [ ObjectId
("57b137bca64ba6296d0df2d1"), ObjectId("57b137caa64ba6296d0df2d2") ], "__v" : 2 }


and the comment collection:

{ "_id" : ObjectId("57b135b9faa8fcff66b94e3f"), "author" : "Tyler", "text" : "Ok", "rating" : 5, "movie" : [ ], "__v" : 0 }
{ "_id" : ObjectId("57b135c5faa8fcff66b94e40"), "author" : "Jennicaa", "text" : "asdlfjljsf", "rating" : 1, "movie" : [ ], "__v" : 0 }
{ "_id" : ObjectId("57b137bca64ba6296d0df2d1"), "author" : "Bentley", "text" : "Its amazing", "rating" : 5, "movie" : [ ], "__v" : 0 }
{ "_id" : ObjectId("57b137caa64ba6296d0df2d2"), "author" : "Lucy", "text" : "Its pretty good", "rating" : 4, "movie" : [ ], "__v" : 0 }


I'm noticing that the comment collection is not associating the movie id as it is just an empty array. That must be my issue?

I just want the average of the reviews for each movie by title. So if a movie titled Star Trek had four reviews with scores of 3, 3, 4, 5, the average would be 3.75. Normally I would just sum all the numbers in an array and then divide by the length of the array, but I just don't know enough with mongoDB/mongoose to do that :(

Sorry for not providing the proper amount of information straight away.

Answer

Since the comment model has an array which holds the movies, you need to unwind the array field first before calculating aggregates based on that group. Your aggregation pipeline would have an $unwind step before the $group to flatten the array (denormalize it). You can then group the flattened documents by the movie _id and calculate the average.

The following example shows this:

Comment.aggregate([
    { "$unwind": "$movie" },
    { 
        "$group": {
            "_id": "$movie",
            "ratingAvg": { "$avg": "$rating" }
        }
    }
], function(err, results) {
    if(err) handleError(err);
    Movie.populate(results, { "path": "_id" }, function(err, result) {
        if(err) handleError(err);
        console.log(result);
    });
})