Adding up review totals in mongoose - javascript

I am creating a website that helps you find doctors. I would like the doctors to have a field in their model that has an overall rating, friendliness rating, cleanliness rating, satisfaction rating, and responsiveness rating. All of these ratings will be a number calculated by adding up all of their review (another model) and populating these fields using a sum calculation. My question is, how do I accomplish this when all of my reviews are a separate model that is virtually populated on the doctor's model in a controller? Is there some way to add up all of the reviews for a specific doctor ID and then calculate averages on the ratings and then update the doctor with this information?
Here is my doctor model:
const prothesistSchema = mongoose.Schema(
{
name: {
type: String,
required: [true, 'A user must have a name'],
},
email: {
type: String,
required: [true, 'A user must have an email'],
unique: true,
lowercase: true,
validate: [validator.isEmail, 'Please provide a valid email'],
},
ratingsAverage: {
type: Number,
default: 4.5,
min: [1, 'Rating must be above 1.0'],
max: [5, 'Rating must be below 5.0'],
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
prothesistSchema.virtual('reviews', {
ref: 'Review',
foreignField: 'prosthetistID',
localField: '_id',
});
const Prothesist = mongoose.model('Prothesist', prothesistSchema);
Here are the reviews I am trying to calculate averages off of:
const reviewSchema = new mongoose.Schema(
{
review: {
type: String,
required: [true, 'A review must have a review!!!'],
},
rating: {
type: Number,
min: 0.5,
max: 5,
},
friendliness: {
type: Number,
min: 0.5,
max: 5,
},
cleanliness: {
type: Number,
min: 0.5,
max: 5,
},
satisfaction: {
type: Number,
min: 0.5,
max: 5,
},
responsiveness: {
type: Number,
min: 0.5,
max: 5,
},
createdAt: {
type: Date,
default: Date.now,
},
prosthetistID: {
type: mongoose.Schema.ObjectId,
ref: 'Prothesist',
required: [true, 'Review must belong to a prosthetist!'],
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
require: [true, 'Each review must have an associated user!'],
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
reviewSchema.pre(/^find/, function (next) {
const query = [
// { path: 'tour', select: 'name' },
{ path: 'user', select: 'name photo activityLevel age amputationInfo' },
];
this.populate(query);
next();
});
const Review = mongoose.model('Review', reviewSchema);

2 approaches here:
The first one would be keeping a running score and keep editing that instead of calculating from the beginning. I am assuming this is what big sites (like Yelp or Google) are doing, since it's way more scalable.
The second and most obvious approach would be, iterating over all the reviews every time a review gets added to calculate new numbers.
Let's dissect the first one:
save a running score for every field under every doctor's listing
trigger your updateRatings function on every addition to the reviews
this function should:
Get current score
Multiply it by the reviews.length
Add the currentReviewRating
Divide by review.length+1 to get the new average
Save that as a new average to use later
The second one would be very straight forward:
let avg = avg([...reviews.field]) => ( sum(arr) / length(arr) );

Related

Calculate a sum from array of refs before every save and update

Suppose you have two mongoose schemas: Meal and Ingredient.
Meal
const mealSchema = new mongoose.Schema({
title: { type: String },
ingredients: [{
ingredient: { type: mongoose.Types.ObjectId, required: true, ref: 'Ingredient' },
amount_grams: { type: Number, required: true }
}],
});
Ingredient
const ingredientSchema = new mongoose.Schema({
name: { type: String, required: true },
kcal_per_gram: { type: Number, required: true },
});
Before every save and update, I want to calculate the kcal property and assign it to the Meal model. The pseudocode for calculation is this:
let sum_kcal = 0;
for (ingr of ingredients) {
sum_kcal += ingr.ingredient.kcal_per_unit * ingr.amount;
}
I can't use a virtual property, because I need the user to be able to sort and filter by kcal.

MongoDB Schema for interview time slot availability

I'm building a website where admins can create interviews by selecting participants,
interview start time and end time. I have divided the participants into two groups(collections) - Applicants and Team_Members.
I tried creating a 3rd collection called Interviews to keep track of the start and end times for each interview but I don't think that there's a need for a 3rd collection now.
So far, these are the schemas I have come up with -
const applicantSchema = new Schema({
name: {
type: String,
trim: true,
required: [true, "Name is required"],
},
image: {
type: String,
},
interviews: [
{
start_time: String,
end_time: String,
},
],
});
const interviewerSchema = new Schema({
name: {
type: String,
trim: true,
required: [true, "Name is required"],
},
image: {
type: String,
default: "download.png",
},
interviews: [{
start_time: String,
end_time: String,
}, ],
});
How should I update the interviews property once each new interview is booked? And am I going in the right direction in terms of forming the schemas for the problem required?
You could use the same schema for both. Just add interviewee: { type: boolean, required: true } and add that criteria when you do a search.
for the interviews start and end times, change the values to Date, like that you will be able to search them and find dates > $gt or < $st to make sure you don't double book a time slot. For marking booked, simply add another value called `booked: { type: boolean, default: false'
const interviewSchema = new Schema({
name: {
type: String,
trim: true,
required: [true, "Name is required"],
},
image: {
type: String,
},
interviewee: {
type: Boolean,
required: true
},
interviews: [
{
start_time: {
type: Date,
default: new Date()
},
end_time: {
type: Date,
default: new Date()
},
booked: {
type: Boolean,
default: false
}
},
],
});

How to optimize performance with CREATE, PUT, and DELETE requests on MongoDB?

I have a database named "reviews" with a 9.7GB size. It has a collection name products. I was able to optimize the READ request using indexing technical by running the command db.products.ensureIndex({product_name: 1}); When I run the following command db.products.find({product_name:"nobis"}).explain("executionStats"); in MongoDB terminal, it shows that my execution time reduces from 28334ms to 3301ms.
I have the following 2 questions:
1) How do I use explain("executionStats"); on CREATE, PUT and DELETE requests? For example, I got this following error [thread1] TypeError: db.products.insert(...).explain is not a function when I tried to use the following insert function
db.products.insert({"product_id": 10000002,"product_name": "tissue","review": [{"review_id": 30000001,"user": {"user_id": 30000001,"firstname": "Peter","lastname": "Chen","gender": "Male","nickname": "Superman","email": "hongkongbboy#gmail.com","password": "123"},"opinion": "It's good","text": "It's bad","rating_overall": 3,"doesRecommended": true,"rating_size": "a size too big","rating_width": "Slightly wide","rating_comfort": "Uncomfortable","rating_quality": "What I expected","isHelpful": 23,"isNotHelpful": 17,"created_at": "2007-10-19T09:03:29.967Z","review_photo_path": [{"review_photo_id": 60000001,"review_photo_url": "https://sdcuserphotos.s3.us-west-1.amazonaws.com/741.jpg"}, {"review_photo_id": 60000002,"review_photo_url": "https://sdcuserphotos.s3.us-west-1.amazonaws.com/741.jpg"}]}, {"review_id": 30000002,"user": {"user_id": 30000002,"firstname": "Peter","lastname": "Chen","gender": "Male","nickname": "Superman","email": "hongkongbboy#gmail.com","password": "123"},"opinion": "It's good","text": "It's bad","rating_overall": 3,"doesRecommended": true,"rating_size": "a size too big","rating_width": "Slightly wide","rating_comfort": "Uncomfortable","rating_quality": "What I expected","isHelpful": 23,"isNotHelpful": 17,"created_at": "2007-10-19T09:03:29.967Z","review_photo_path": [{"review_photo_id": 60000003,"review_photo_url": "https://sdcuserphotos.s3.us-west-1.amazonaws.com/741.jpg"}]}]}).explain("executionStats");
2) Is there any performance Optimization method I can use for the CREATE, PUT and DELETE requests? For example, I am able to use POSTMAN to get the response time of a DELETE request, but the response time takes 38.73seconds.
const deleteReview = (request, response) => {
const id = parseInt(request.params.id);
Model.ProductModel.findOneAndDelete({ "review.review_id": id}, (error, results) => {
if (error) {
response.status(500).send(error);
} else {
response.status(200).send(results);
}
});
};
This is my MongoDB schema:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/reviews', { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });
const Schema = mongoose.Schema;
const productSchema = new Schema({
product_id: { type: Number, required: true, unique: true },
product_name: { type: String, required: true, unique: true },
review: [{
review_id: { type: Number, required: true, unique: true },
user: {
user_id: { type: Number },
firstname: { type: String },
lastname: { type: String },
gender: { type: String, enum: ['Male', 'Female', 'Other'] },
nickname: { type: String },
email: { type: String, required: true },
password: { type: String, required: true },
},
opinion: { type: String, required: true },
text: { type: String },
rating_overall: { type: Number, min: 1, max: 5, required: true },
doesRecommended: { type: Boolean, required: true },
rating_size: { type: String, enum: ['a size too small', '1/2 a size too small', 'Perfect', '1/2 a size too big', 'a size too big'], required: true },
rating_width: { type: String, enum: ['Too narrow', 'Slightly narrow', 'Perfect', 'Slightly wide', 'Too wide'], required: true },
rating_comfort: { type: String, enum: ['Uncomfortable', 'Slightly uncomfortable', 'Ok', 'Comfortable', 'Perfect'], required: true },
rating_quality: { type: String, enum: ['Poor', 'Below average', 'What I expected', 'Pretty great', 'Perfect'], required: true },
isHelpful: { type: Number, required: true, default: 0 },
isNotHelpful: { type: Number, required: true, default: 0 },
created_at: { type: Date, required: true },
review_photo_path: [{
review_photo_id: { type: Number },
review_photo_url: { type: String }
}]
}]
});
const ProductModel = mongoose.model('product', productSchema);
module.exports = { ProductModel };
If you do not have one, ensure you have an index of review.review_id on your products collection. You're using that to look up what to delete so it should be indexed.
I read your deleteReview function as deleting the product document that contains the review, not just removing the individual review -- is that what you expect?
You should be able to just $pull the review from the reviews array to get rid of it.
You can use explain on an update like so:
db.products.explain().update({...}, {...});
See: https://docs.mongodb.com/manual/reference/method/db.collection.explain/
You can explain:
aggregate()
count()
find()
remove()
update()
distinct()
findAndModify()

E11000 duplicate key error collection: Inserting document in mongodb

I am seeding my database for testing so I have inserted 15000 instructor data in database now for each instructor I want to insert 100 course. so I ran to for loop
first to get all instructor ids and second to store 100 course for that id of instructor but while inserting courses I get this type of error
E11000 duplicate key error collection: Courser.courses index: ratings.user_1 dup key: { : null }
Here is the code to enter course for each instructor
seedCourse: async (req, res, next) => {
try {
const instructors = await Instructor.find();
//const insrtuctor contains 15000 instructor
for(let oneInst of instructors) {
for(let i=0; i<=100; i++) {
const course = await new Course({
title: faker.lorem.sentence(),
description: faker.lorem.paragraph(),
author: oneInst._id,
prise: Math.floor(Math.random()*6 + 4),
isPublished: 'true',
tags: ["java", "Nodejs", "javascript"]
});
const result = await course.save();
await Instructor.findByIdAndUpdate(oneInst._id, { $push: { courses: result._id } });
console.log(`Instructor Id ${oneInst._id} Course added ${i}`);
}
}
} catch (error) {
next(error)
}
}
My course model definition looks something like this
const mongoose = require('mongoose');
const Course = mongoose.model('courses', new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 3
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'instructor'
},
description: {
type: String,
required: true,
minlength: 5
},
ratings: [{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users',
required: true,
unique: true
},
rating: {
type: Number,
required: true,
min: 0,
max: 5
},
description: {
type: String,
required: true,
minlength: 5
}
}],
tags: [String],
rating: {
type: Number,
min: 0,
default: 0
},
ratedBy: {
type: Number,
min: 0,
default: 0
},
prise: {
type: Number,
required: function() { this.isPublished },
min: 0
},
isPublished: {
type: Boolean,
default: false
}
}));
module.exports = Course;
In your Course schema user in ratings array is an unique field. You are not giving any unique value while storing course in DB. First time it tool value as null but next time it is trying to save null value for user. Hence violating the schema.
Either remove unique:true or pass an unique value for user

Mongoose: Join Operation, populate isn't possible

I'm currently struggling with a project of mine.
I've got a collection called "Games" and one "Participation".
When a user loggs in, he/she should be able to see the games and the individual participation status.
Therefore, I want to join these two collections but I can't use .populate() because I can't enter the neccessary Participation ID in the Games collection due to the fact, that I don't have the participation ID at the time I create a game. (So I would need to save the participation, remember the created ID and insert THIS id in the games collection afterwards)
The other way round would be a good solution (to populate Games in Participation) but initially, there are no participations in these collection, until a user clicks "Participate" or "Not Participate".
Basically I need a SQL query like that:
select * from games g, participation p where g.gamesId = p.gamesId AND p.username = [...]
Is there any possibility to achieve this?
Otherwise I would need to save every participation as a "Game", having the dependent username and his/her participation status in it.
Wouldn't prefer this solution at all.
Thanks in advance!
In case it helps:
Here are my two collections:
Game:
var gameSchema = mongoose.Schema(
{
isHome: { type: Boolean, required: true },
time: { type: String, required: true, max: 100 },
uzeit: { type: String, required: true, max: 100 },
clubId: { type: mongoose.Types.ObjectId, required: true },
enemy: { type: String, required: true, max: 100 },
place: { type: String, required: true, max: 100 },
(participation: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Teilnahme' }])
});
Participation:
var participationSchema = mongoose.Schema(
{
playerAvailable: { type: Boolean, required: true },
clubId: { type: mongoose.Types.ObjectId, required: true },
gameId: { type: mongoose.Types.ObjectId, required: true, ref: 'Game' },
memberName: { type: String, required: true },
}
);

Categories