Related
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) );
I want to design a schema for storing Contacts of Users.
Here are my existing schemas:
User Schema
_id : ObjectId("5c53653451154c6da4623a77"),
name : “something”,
email : “something”,
password : “something”,
Profile Schema
"_id" : ObjectId("5c53653451154c6da4623a88"),
user_id - ref
mobile : “something”,
company” : “something”,
designation : “something”,
website : “something”,
social: {
youtube: {
type: String
},
twitter: {
type: String
},
facebook: {
type: String
},
linkedin: {
type: String
},
instagram: {
type: String
}
}
I can think of two approaches to the Contact schema but both have some cons:
First approach
"_id" : ObjectId("5c53653451154c6da4623a99"),
user_id - ref,
"contacts": [
{
name : “something”,
company : “something”,
designation : “something”,
website : “something”,
social: { something },
mobile : “something”
},
{
name : “something”,
company : “something”,
designation : “something”,
website : “something”,
social: { something },
mobile : “something”
},
...
]
The problem with the above structure is that when the User updates their Profile the Contact fields can not get the updated value. But in this approach, it is easy to query and retrieve all Contacts of a particular User and send the response back.
Second approach
"_id" : ObjectId("5c53653451154c6da4623a99"),
user_id : ref,
contacts: [
profile_id,
profile_id,
profile_id,
profile_id,
profile_id,
profile_id,
...
]
In this structure Contacts have the updated User value when the User updates their Profile. But the problem here is while querying I have to fetch the profile id from the Contact schema, then query the Profile schema and return the value to the client as a response.
What happens when there are 30K-50K contacts - do I need to query the DB 50K times? Or is there a better approach?
Building on node.js, using mongoose.
Basically you have a scenario where relational database will be required. But You can also achive this in mongo .
You need to use populate of mongoose. With your second approach. Where you storing profile ids.
User.find({_id: '5c53653451154c6da4623a77'}).populate({
path:'profiles',
options: {
limit: 10,
skip: 0
}}).exec();
Mongoose populate
This query will return related profiles. If you have data like 50K. You must limit the data in one request.
NOTE: Mongodb limit for per document is 16mb. It is not possible to store that much data.
So, Just rethink your database.
If I understand you correctly I think you've correctly identified some pros/cons of each option. Now you have to decide what makes sense for your specific case. Option 1 will be easy for fetching but tedious for updating and keeping in sync with Profiles. Option 2 has more normalized data and will be better for updating but will require more queries to retrieve. So you have to ask yourself some questions.
How important is having normalized data to you?
How will the size of your Profile vs Contact schemas compare? Will you have significantly more Profiles? Significantly less? Orders of magnitude?
What happens more often - that someone updates their profile or that someone queries for contacts? How much more? Orders of magnitude?
To dive deeper into your last two answers - do some estimates, even rough ones if you have to, and then do some math to see what might make more sense.
For example, if you will have 10,000 contacts per user then option #1 will give you a much larger document that can gather all contacts and profiles in a single query. If your users only update their profiles say, on average, once a month but you need to query contacts several times a day this might be the better option. However, if you have users who like to update their profile daily and you need to query contacts maybe once a week then option #2 could make more sense.
At the end of the day it's a design choice that is very specific to your scenario. Good luck!
There are 2 schemas one is for the user and another for orders. One user can create multiple orders like your contacts.
user-
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const bcrypt = require("bcryptjs");
const userSchema = new Schema(
{
firstName: {
trim: true,
type: String,
required: [true, "firstName is required!"],
validate(value) {
if (value.length < 2) {
throw new Error("firstName is invalid!");
}
}
},
lastName: {
trim: true,
type: String,
required: [true, "lastName is required!"],
validate(value) {
if (value.length < 2) {
throw new Error("lastName is invalid!");
}
}
},
email: {
unique: [true, "Email already registered"],
type: String,
required: [true, "Email is required"]
},
password: {
trim: true,
type: String,
require: [true, "Password is required"],
validate(value) {
if (value.length < 6) {
throw new Error("Password should be atleast 6 characters");
}
}
},
mobile: {
trim: true,
unique: [true, "Mobile Number already available"],
type: String,
required: [true, "Mobile Number is required"],
validate(value) {
if (value.length !== 10) {
throw new Error("Mobile Number is invalid!");
}
}
},
gender: {
trim: true,
type: String,
enum: [
"Male",
"Female"
],
required: [true, "Password is required"],
},
dob: {
trim: true,
type: Date,
required: [true, "DOB is required"],
},
Address: {
address: {
trim: true,
type: String,
require: [true, "Owner Address is required"]
},
city: {
trim: true,
type: String,
require: [true, "Owner Address City is required"]
},
state: {
trim: true,
uppercase: true,
type: String,
require: [true, "Owner Address State is required"]
},
pin: {
trim: true,
uppercase: true,
type: Number,
require: [true, "Owner Address Pin is required"],
validate(value) {
if (!(value >= 100000 && value <= 999999)) {
throw new Error("Pin is invalid!");
}
}
}
}
},
{
timestamps: true
}
);
const Users = mongoose.model("Users", userSchema);
module.exports = Users;
Order-
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const orderSchema = new Schema({
userId: {
trim: true,
type: Schema.Types.ObjectId,
ref: "users",
required: [true, "User ID is required"]
},
dateOfOrder: {
trim: true,
type: Date,
required: [true, "Date of Order is required"],
},
dateOfMeasurement: {
trim: true,
type: Date,
},
dateOfTrail: {
trim: true,
type: Date,
},
dateOfDelivery: {
trim: true,
type: Date,
},
},
{
timestamps: true
});
const Orders = mongoose.model("Orders", orderSchema);
module.exports = Orders;
The problem you have is actually why mongodb is not the best choice for this scenario. Your data is relational, but you are using a non-relational database.
But for your situation there are some steps to make it better:
First approach
You can choose the first approach and when a user data is updated, also update all contacts. Easier query but hard to keep data in sync.
Second approach
Or you can choose the second approach, but instead of just saving the ref_id also save the contact name (the field is mostly gonna be used for querying). this way it would be easier to keep the data in sync and if search is done by name, you could just do a normal find.
I have the following schema:
const postsStatsSchema = new Schema({
postid: {
type: String,
maxlength: [128, "post ID must be at most 128"],
required: true
},
likecounter: {
type: Number,
default: 0,
min: 0,
required: true
}
});
const userStatsSchema = new Schema({
username: {
type: String,
required: true,
maxlength: [50, "Name length must be at most 50"]
},
posts: {
type: [postsStatsSchema],
required: true
}
});
const statisticsSchema = new Schema({
month: {
type: Number,
required: true
},
users: {
type: [userStatsSchema],
required: true
}
}, {
timestamps: true
});
I'm trying to increment (or create, if the document does not exist) the 'likecounter' in the postsStats object. I tried many different ways, but non were successful. This is the last thing I tried:
let update = {};
update['$inc'] = {};
update['$inc'] = {'users.$.username.user.$.posts.somepost.likecounter': 1000};
try {
const res = await stats.findOneAndUpdate({month: 100}, update, {'upsert': true});
console.log(res);
} catch (err) {
console.log(err);
}
The error I'm getting on the code above is:
MongoError: Too many positional (i.e. '$') elements found in path
I tried many variations, with and without the '$' sign, but only managed to increment the month. I believe it is something with the nested document that Im doing wrong but just can't figure out what it is.
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 },
}
);
I'm trying to accomplish something really easy but still manage to fail.
What I am trying to do is when I get a get request on my server I want to return all documents BUT just the specific fields populated.
My schema goes as follows
var clientSchema = new Schema({
name:{
type: String,
required: true
},
phone:{
type: String,
required: true
},
email:{
type: String,
required: true
},
address: {
type: String,
required: false
}
});
var orderDetailsSchema = new Schema({
//isn't added to frontend
confirmed:{
type: Boolean,
required: true,
default: false
},
service:{
type: String,
required: true
},
delivery:{
type: String,
required: false
},
payment:{
type: String,
required: false
},
status:{
type: String,
required: true,
default: "new order"
},
});
var orderSchema = new Schema({
reference:{
type: String,
required: true
},
orderdetails: orderDetailsSchema,
client: clientSchema,
wheelspec: [wheelSchema],
invoice:{
type: Schema.Types.ObjectId,
ref: 'Invoice'
}
});
What I want is to return only client.phone and client.email plus orderdetails.status but still retain reference field if possible
I have tried using lean() and populate() but had no luck with them. Is there anything utterly simple I am missing? Or what I am trying to achieve is not that easy?
Thanks!
You can specify the fields to return like this:
Order.findOne({'_id' : id})
.select('client.phone client.email orderdetails.status reference')
.exec(function(err, order) {
//
});
Alternative syntax
Order.findOne({'_id' : id})
.select('client.phone client.email orderdetails.status reference')
.exec(function(err, order) {
//
});
I've made a number of assumptions here, but you should be able to see the idea.
Simply do like this :-
Order is model name which is registered in mongoose.
Order.findById(id) // set id you have to get
. populate('client')
.select('client.phone client.email orderdetails.status reference')
.exec(function(err, order) {
//
});
You can use projection.
await Order.findById(diaryId, {phone: 1, email: 1, status: 1})
If phone:1 is set to 1, it is included, if phone:0, then it's excluded.