Proper way of updating average rating for a review system using Mongoose

Viewed 666

I'm currently learning some backend stuff using an Udemy course and I have an example website that lets you add campgrounds (campground name, picture, description, etc.) and review them. I'm using the Express framework for Node.js, and Mongoose to access the database.

My campground schema looks like:

const campgroundSchema = new mongoose.Schema({
    name: String,
    image: String,
    description: String,
    price: String,
    comments: [
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: "Comment"
        }
    ],
    rating: {type: Number, default: 0}
});

And my comment/review schema looks like:

const commentSchema = new mongoose.Schema({
    text: String,
    rating: {
        type: Number,
        min: 1,
        max: 5,
        validate: {validator: Number.isInteger}
    },
    campground: {type: mongoose.Schema.Types.ObjectId, ref: "Campground"}
});

Campgrounds and Comments also have references to a User but I've left that out for simplicity.

I'm looking to know the best practice for updating and displaying the campground average rating.

The method used by the tutorial I'm following is to recalculate the average rating each time a comment is added, changed, or deleted. Here's how it would work for a new comment:

Campground.findById(campgroundId).populate("comments").exec(function(err, campground) {
    Comment.create(newComment, function(err, comment) {
        campground.comments.push(comment);
        campground.rating = calculateRating(campground.comments);
        campground.save();
    });
});

"calculateRating" iterates through the comment array, gets the total sum, and returns the sum divided by the number of comments.

My gut instinct tells me that there should be a way to make the "rating" field of Campground perform the functionality of the "calculateRating" function, so that I don't have to update the rating every time a comment is added, changed, or removed. I've been poking around documentation for a while now, but since I'm pretty new to Mongoose and databases in general, I'm a bit lost on how to proceed.

In summary: I want to add functionality to my Campground model so that when I access its rating, it automatically accesses each comment referenced in the comments array, sums up their ratings, and returns the average.

My apologies if any of my terminology is incorrect. Any tips on how I would go about achieving this would be very much appreciated!

Love,

Cal

1 Answers

I think what you are trying to do is get a virtual property of the document that gets the average rating but it does not get persisted to the mongo database. according to mongoosejs :- Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. They are set on the schema. You can do this:

CampgroundSchema.virtual('averageRating').get(function() {
  let ratings = [];
  this.comments.forEach((comment) => ratings.push(comment.rating));
  return (ratings.reduce((a,b)=>a+b)/ratings.length).toFixed(2);
 });

After that on your view engine after finding campgrounds or a campground, all you need to call is ; campground.averageRating;

Read more here : https://mongoosejs.com/docs/guide.html#virtuals

also note that you can not make any type of query on virtual properties.

Related