Reputation: 1455
I am trying to calculate the average value of the ratings for a Product. Instead of calculating the average rating of the Product every time when we need the value. I calculate it every time someone rates it. It looks like I am not doing it the right way but I am not sure where my mistake is. Let me know what I am doing wrong. Update - Now I understand why I am not getting any results. I am using post('save')
instead of post('updateOne')
but the implementation of the latter one is not very straightforward in the docs. Very confusing.
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Schema;
const ParentSchema = new mongoose.Schema(
{
title: {
type: String,
trim: true,
required: true,
maxlength: 32,
text: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
index: true,
},
price: {
type: Number,
required: true,
trim: true,
maxlength: 32,
},
quantity: Number,
ratings: [
{
star: Number,
postedBy: { type: ObjectId, ref: 'User' },
},
],
},
{
timestamps: true,
}
);
ParentSchema.statics.avgRating = async function (id) {
try {
const stats = await this.model('Product').aggregate([
{
$group: {
_id: id,
avgRating: { $avg: '$ratings.star' },
nRatings: { $sum: 1 },
},
},
]);
console.log({ stats });
} catch (err) {
console.log(`avgRating error from Post SAVE hook${err}`);
}
};
// Call avgRating before save
ParentSchema.post('save', async function () {
await this.constructor.avgRating(this._id);
});
export default mongoose.models.Product ||
mongoose.model('Product', ParentSchema);
Upvotes: 1
Views: 953
Reputation: 59446
Not clear what you are looking for, but I would simply use
this.model('Product').aggregate([
{
$addFields: {
avgRating: { $avg: "$rating.star" },
nRatings: { $size: "$rating" }
}
}
])
Upvotes: 1
Reputation: 8894
You can use $group
with $avg
db.collection.aggregate([
{ "$match": { _id: "1" } },
{ $unwind: "$rating" },
{
"$group": {
"_id": "$_id",
"title": { "$first": "$title" },
"rating": { "$push": "$rating" },
avg: { $avg: "$rating.star" }
}
}
])
Working Mongo playground
Update 1.
You can easlily do with $addFields
. Thanks to @Wernfried Domscheit
db.collection.aggregate([
{ "$match": { _id: "1" } },
{
"$addFields": {
"average": { "$avg": "$rating.star" }
}
}
])
Working Mongo playground
Upvotes: 1