I'm trying to update a model with the following query:
Model.findByIdAndUpdate(id, { min_age: 18, max_age: 24 }, { new: true, runValidators: true, setDefaultsOnInsert: true })
The thing is that I need to validate that those values aren't opposed to each other like the following query
Model.findByIdAndUpdate(id, { min_age: 24, max_age: 18 }, { new: true, runValidators: true, setDefaultsOnInsert: true })
the schema I built so far is this one:
let ageSchema = new mongoose.Schema({
min_age: {
type: Number,
min: 18,
max: 80,
required: true,
validate: {
validator: function(value) {
return this.max_age >= value
},
message: 'min_age has to be lower than or equal to max_age'
}
},
max_age: {
type: Number,
min: 18,
max: 80,
required: true,
validate: {
validator: function(value) {
return this.min_age <= value
},
message: 'max_age has to be greater than or equal to min_age'
}
}
})
The validation works for creating the model, but unfortunately, it doesn't work for updating the data.
When I'm creating the model, the this keyword refers to the model, which gives me access to other properties on this model.
When I'm updating the model, the this keyword refers to global, which doesn't give me access to other properties on this model.
Is there any practical way to validate those properties based on other properties during an update?
Thanks.
Related
I needed a property of date/time which would allow to me get the time at which a certain task was created, I added timestamp property and set it to be true,
But I m not able to compile my code.
The code is perfectly running fine without the timestamp property
const mongoose = require("mongoose");
const Task = mongoose.model(
"Task",
({
title: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: true,
trim: true,
minLength: 100,
},
completed: {
type: Boolean,
default: false,
},
},
{ timestamps: true })
);
module.exports = Task;
I needed a property of date/time which would allow to me get the time at which a certain task was created, I added timestamp property and set it to be true,
But I m not able to compile my code.
The mongoose.model() function of the mongoose module is used to create a collection of a particular database of MongoDB. The name of the collection created by the model function is always in plural format mean GFG to gfss and the created collection imposed a definite structure.
Syntax:
mongoose.model(<Collectionname>, <CollectionSchema>)
Parameters: This function accepts the following two parameters:
Collection name: It is the name of the collection.
Collection Schema: It is the schema of the collection.
Return type: This function returns the Mongoose object.
You need to pass a valid schema for the second argument like below
const mongoose = require("mongoose");
const TodoModel = mongoose.model(
"Task",
new mongoose.Schema(
{
title: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: true,
trim: true,
minLength: 100,
},
completed: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
)
);
module.exports = TodoModel;
More about what is a valid schema refer below
https://mongoosejs.com/docs/schematypes.html
I'm developing an online store Node.js REST API with Mongoose (MongoDB), which I'm new to. I decided to test the orders service and saw that after I had made 1 successful order (so it worked once), it sent me a duplicate key error for the next one, for a key 'name' with value 'null', of the order.products collection that is an Array, and not a kvp object.
I should note that nowhere in my code is 'products.name' mentioned.
ERROR:
MongoServerError: E11000 duplicate key error collection: store.orders index: products.name_1 dup
at {...}{
key: { products.name: null }
index: 0,
code: 11000,
keyPattern: { 'products.name': 1 },
keyValue: { 'products.name': null },
[Symbol(errorLabels)]: Set(0) {}
}
when the error is handled, this message is received and it makes no sense:
{ "message": "Order with products.name "null" already exists" }
Order schema:
const schema = new Schema({
userId: {
type: Types.ObjectId,
ref: 'User'
},
address: {
type: addressSchema,
required: true
},
products: {
type: [orderProductSchema],
required: true,
validate: nonEmptyArray
},
status: {
type: Number,
validate: inCollection(Object.values(ORDER_STATUS))
},
price: { type: Number, required: true, min: 0 }
}, { timestamps: true });
don't bother with the validators or the address/status/user/price, it has nothing to do with them; what is more, nothing is specified as unique: true
As you can see, the 'products' field is just an array of products, no 'name' is declared
orderProductSchema:
const schema = new Schema({
product: {
_id: { type: Types.ObjectId, required: true },
name: {
type: String,
required: true,
maxLength: 250
},
displayImage: String,
price: { type: Number, required: true, min: 0 }
},
quantity: {
type: Number,
required: true,
validate: isInteger,
min: 1
},
}, { _id: false });
I have a 'name' field here, but it's just the name of a product. The error is thrown even when the names are unique.
Orders service:
// get data and format it to fit the Order model
console.dir(products); // --> all is how it's expected to be in the schema
return await Order.create({
userId,
address,
products,
status: ORDER_STATUS.AWAITING_CONFIRMATION,
price: totalOrderPrice
});
It seems to me that this is some weird MongoDB behaviour/specification that I missed. If someone has any idea what could cause the problem - please help.
I tried removing all parts such as other fields and validations to see if they might've caused the problem but that was not the case. I thought maybe I had formatted the object I send to the database wrong, but I console.log-ed it and it was fine ({products: Array})
Thanks to #user20042973 and #MTN I saw that my 'orders' database had index 'products.name' (no idea how it got there).. I just removed the index and the problem is solved.
I have collection like this.
const Device: Schema = new Schema(
{
location: {
type: String,
required: true,
},
macAddress: {
type: String,
required: true,
immutable: true,
},
ip: {
type: String,
required: true,
immutable: true,
},
},
{
timestamps: true,
versionKey: false,
collection: 'Device',
}
);
I want to update immutable fields with my endpoints and i use this function and doesnt work.
Device.findOneAndUpdate(
{ _id: req.params.mdeviceId },
{ $set: { macAddress: req.body.macAddress, ip: req.body.ip},
{ new: true, upsert: true }
);
How can i update this specific fields?
I would guess req.params.mdeviceId is a string and needs to be casted to ObjectId, this is not related to the immutable property as you are providing the new flag which allows to bypass the schema protection, try using this:
Device.findOneAndUpdate(
{ _id: new mongoose.Types.ObjectId(req.params.mdeviceId) },
{ $set: { macAddress: req.body.macAddress, ip: req.body.id},
{ new: true, upsert: true }
);
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 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.