I have just started learning the MERN stack and I am having trouble updating a text within a model with Express/Node. I tried to look for help and accessed Update a model within a model
How to Nest Models within a Model but they weren't quite what I was looking for.
I am working with 2 models, with the comments model embedded within the cats models like so. This is the comment model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const commentSchema = new Schema(
{
user_id: { type: String, required: true },
cat_id: { type: String, required: true },
text: {
type: String,
min: [3, "Comment cannot be too short"],
},
email: { type: String, required: true },
},
{ timestamps: true }
);
const Comment = mongoose.model("Comment", commentSchema);
module.exports = Comment;
And this comment model is within the cat models
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Comment = require("./comments.js");
const catSchema = new Schema(
{
name: {
type: String,
required: true,
unique: true,
min: [2, "Cat name minimum of 2 characters."],
},
description: { type: String, required: true },
image: { type: String },
gender: { type: String, required: true },
cage: { type: String, required: true },
adoptable: { type: String, required: true },
comments: [Comment.schema],
},
{ timestamps: true }
);
const Cat = mongoose.model("Cat", catSchema);
module.exports = Cat;
In my controller, when I update a comment, I need to update the respective comment inside the cat model too, but I'm not able to do so. I tried targeting the particular cat, foundCat, and I can't access the comment with foundCat.comments.id(req.params.id)
Strangely enough, when I console log "foundCat.comments.id", it tells me that this is a function? So I don't know why I can't access and update that text...
Here is my code for updating the comment: Note! The part with issue is located at the end, look for "Cat.findOne"
// For updating comment
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a body to update",
});
}
// req.body exists, so find the comment by id and then update
Comment.findOne({ _id: req.params.id }, (err, comment) => {
if (err) {
return res.status(404).json({
err,
message: "Comment not found!",
});
}
console.log(req.body);
// update the comment details
comment.text = req.body.text;
// save the updated comment
comment
.save()
.then(() => {
// return json response if successful
return res.status(200).json({
success: true,
id: comment._id,
message: "Comment updated!",
});
})
.catch((error) => {
return res.status(404).json({
error,
message: "Comment not updated!",
});
});
// now update the comment entry for the cat too
Cat.findOne({ _id: comment.cat_id }, (err, foundCat) => {
console.log("This doesnt work", foundCat.comments.id(req.params.id));
foundCat.save((err, updatedCat) => {
console.log(updatedCat);
});
});
});
};
Example of the comments within a cat:
You should update the cat instance once the comment has been fetched.
Try to change your code like this (using async wait):
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: 'You must provide a body to update',
});
}
try {
// req.body exists, so find the comment by id and then update
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({
err,
message: 'Comment not found!',
});
}
// update the comment details
comment.text = req.body.text;
// save the updated comment
await comment.save();
// now update the comment entry for the cat too
const cat = await Cat.findById(comment.cat_id);
const otherCatComments = cat.comments.filter((c) => c._id !== comment._id);
cat.comments = [...otherCatComments, comment];
await cat.save();
res.status(200).json({
success: true,
id: comment._id,
message: 'Comment updated!',
});
} catch (err) {
res.status(404).json({
error,
message: 'Comment not updated!',
});
}
};
Luca, thank you! That was extremely helpful and I can see the appended comment added to the cats comment array. Now the only thing is the cats.comment.filter isn’t quite working as intended, as the otherCatsComments still includes all the comments. I had to do a little digging in the code and I managed to console log the id, which returns “_id: new ObjectId("617d57719e815e39f6049452"),”
I tried changing it to
const otherCatComments = cat.comments.filter((c) => c._id !== `new ObjectId("${comment._id}")`);
const otherCatComments = cat.comments.filter((c) => c._id !== ` new ObjectId("${comment._id}")`);
const otherCatComments = cat.comments.filter((c) => c._id !== `ObjectId("${comment._id}")`);
But they all don’t seem to work, so I had to do a deep de-bugging and turns out my code is off for some things! I’ll just add them here in case anyone bumps into this issue in the future.
First off, my comment id was different from the comment id within my cats model. For reference, here is my create comment model (I modified it to use the async/await + try/catch block as recommended by Luca:
const createComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a comment",
});
}
try {
// req.body exists, so make a new comment
const comment = new Comment(req.body);
await comment.save();
// now add comment to cat
Cat.findById(req.params.id, (err, foundCat) => {
// Append the comment to the cat
foundCat.comments.push(comment);
foundCat.save();
});
// somehow, if the new comment doesn't exist, return error
if (!comment) {
return res.status(400).json({ success: false, error: err });
}
// success!
res.status(201).json({
success: true,
id: comment._id,
message: "Comment created!",
});
} catch (err) {
return res.status(400).json({
err,
message: "Comment not created!",
});
}
};
Note the part where I add the comments in the cat:
At first it was
foundCat.comments.push(req.body);
but this would generate a comment id in the cat that would be different from the comment id in the comment. so req.body is changed to comment.
Once that was fixed, I tried the original code by Luca, but it still didn’t work. My workaround was to not use the filter, and just delete the old comment and then add in the new comment.
Code here:
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a body to update",
});
}
try {
// req.body exists, so find the comment by id and then update
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({
err,
message: "Comment not found!",
});
}
// update the comment details
comment.text = req.body.text;
// save the updated comment
await comment.save();
// now update the comment entry for the cat too
const cat = await Cat.findById(comment.cat_id);
// remove the old, non-updated comment first
cat.comments.id(comment._id).remove();
// now add in the updated comment
cat.comments.push(comment);
await cat.save();
res.status(200).json({
success: true,
id: comment._id,
message: "Comment updated!",
});
} catch (err) {
res.status(404).json({
error,
message: "Comment not updated!",
});
}
};
Related
I am trying to deleteFeature meanwhile i want all the comments related to that feature deleted but i don't know how to do it.
my deleteFeature method -
exports.deleteFeature = (req, res) => {
try {
const { slug } = req.params;
Feature.findOne({ slug: slug.toLowerCase() }).exec((err, feature) => {
if (err) {
return res.status(400).json({
error: errorHandler(err),
});
}
console.log("Test");
Comment.deleteMany({ _id: feature._id });
console.log("chest");
feature.remove();
console.log("Best");
return res.json({
message: "Your Feature has been Deleted Successfully",
});
});
} catch (error) {
return res.status(400).json({
error: error,
});
}
};
I have this on comment model -
feature: {
type: ObjectId,
ref: "Feature",
required: true,
},
So when i delete a feature, i want to delete all the comments containing that feature's _id on that feature field
Change
Comment.deleteMany({ _id: feature._id });
to
Comment.deleteMany({ feature: feature._id });
I am trying to delete two record from different database (practice log, student) within the same session, the problem is the practice log record is found successfully and shown in console.log(deletePracticeLog), but it cannot be remove from the database. anyone know what have I done wrong? I am very confused.
const deleteStudentById = async (req, res, next) => {
const sid = req.params.sid;
let deleteStudent;
let deletePracticeLog;
try {
deleteStudent = await Students.findById(sid).populate("teacher");
} catch (err) {
const error = new HttpError("Delete failed, please try again.", 500);
return next(error);
}
try {
deletePracticeLog = await PracticeLog.find({ student: sid }).exec();
console.log(deletePracticeLog); // **<-this correctly show the record needed to be deleted, it's a object within an array**
} catch (err) {
const error = new HttpError("Cannot delete, please try again.", 500);
return next(error);
}
if (!deleteStudent) {
const error = new HttpError("Could not find student for this id.", 404);
return next(error);
}
try {
const sess = await mongoose.startSession();
sess.startTransaction();
await deleteStudent.remove({ session: sess }); //delete the student from student database
deleteStudent.teacher.students.pull(deleteStudent); //delete the student id from teacher database
await deleteStudent.teacher.save({ session: sess });
await deletePracticeLog.remove({ session: sess }); // **<-this line caused error, when this line is removed, other code run successfully and other record are deleted.**
await sess.commitTransaction();
} catch (err) {
const error = new HttpError("Cannot delete student with provided id.", 500);
return next(error);
}
res.status(200).json({ message: "Deleted student." });
};
when you are using await, you don't need to exec()
delete your exec() from
deletePracticeLog = await PracticeLog.find({ student: sid }).exec();
change to
deletePracticeLog = await PracticeLog.find({ student: sid })
beacuse you use exec(), use this code for remove data
await deletePracticeLog[0].remove({ session: sess })
if the result of PracticeLog.find({ student: sid }) is one Array and you want to delete all of them, you have to use deleteMany like this:
PracticeLog.deleteMany({ student: sid }).session(sess)
it's worked for me
here is the Mongoose schema:
const mongoose = require("mongoose");
const studentsSchema = new mongoose.Schema({
name: { type: String, require: true },
email: { type: String, require: true },
contact: { type: String, require: true },
teacher: { type: mongoose.Types.ObjectId, require: true, ref: "teachers" },
practiceLogs: [
{ type: mongoose.Types.ObjectId, require: true, ref: "practice_logs" },
],
});
module.exports = mongoose.model("students", studentsSchema);
const practiceLogSchema = new mongoose.Schema({
repertoire: { type: String, require: true },
student: { type: mongoose.Types.ObjectId, require: true, ref: "students" },
teacher: { type: mongoose.Types.ObjectId, require: true, ref: "teachers" },
});
module.exports = mongoose.model("practice_logs", practiceLogSchema);
here is the mongoose.connect():
const url =
"myURL";
mongoose
.connect(url, { useUnifiedTopology: true, useNewUrlParser: true })
.then(() => {
app.listen(5000);
console.log("connected.");
})
.catch((err) => {
console.log(err);
});
i'm newbie with MEAN stack and i have problem when updating data with put Method, i have tested using postman and it works fine, but when i use it on angular its not working. nothing error appear, this what i got in console after updating data
[is give success update][1] but nothing change on data that i updated. i don't have problem with create and delete method, just update method that had problem
here's my code
update.service
updateData(id, data): Observable<any>{
let url = `${this.baseUri}/update/${id}`;
return this.http.put(url,data, { headers : this.headers }).pipe(
catchError(this.errorManagement)
)}
update.component
OnSubmit(id){
let record = this.updateForm.value
if(!record){
this.notif.showError('can\'t do update data','Update data Error')
return false;
}else{
return this.motorService.updateData(id, record).subscribe(res=>{
console.log(record)
},(error) => {
console.log(error)
});
}}}
update route
listDataRoute.route('/update/:id').put((req,res,next)=>{
listData.findByIdAndUpdate(req.params.id,{
$set : req.body
},{new: true, useFindAndModify: false},(error, data)=>{
if (data.n > 0) {
res.status(200).json({
message: 'profile updated'
});
} else {
res.status(401).json({
message: 'not authorized'
});
}
})
.catch(error => {
res.status(500).json({
message: 'updating profile failed'
});
})
})
any idea what i do wrong ? i'm already stuck like 5 hours with this error, thank you
[1]: https://i.stack.imgur.com/ryR8g.png
update : i got error 401 after adding some code in "update route", still don't know how to solve this error
id like to share my way of updating data with the mean stack. its a lil different from your code but it works and is tested with jest. id like to share my example with updating a userprofile
i create a userModel in my backend user.js
// user.js
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
mongoose.set('useCreateIndex', true);
const userSchema = mongoose.Schema({
id: String,
email: { type: String, unique: true },
username: { type: String, unique: true },
password: { type: String, required: true },
currentLocation: String,
age: String,
});
userSchema.plugin(uniqueValidator);
module.exports = mongoose.model('User', userSchema);
then i write the update method in my profileController.js
// profileController.js
exports.update = (req, res, next) => {
const user = new User({
_id: req.body.id,
email: req.body.email,
username: req.body.username,
currentLocation: req.body.currentLocation,
age: req.body.age,
});
User.updateOne({ _id: req.params.id }, user).then(result => {
if (result.n > 0) {
res.status(200).json({
message: 'profile updated'
});
} else {
res.status(401).json({
message: 'not authorized'
});
}
})
.catch(error => {
res.status(500).json({
message: 'updating profile failed'
});
});
}
in my profilerService.ts (frontend) i also define my userclass with the same properties as the user.js model in my backend
// profile.service.ts
export class User {
id: string;
email: string;
username: string;
password: string;
currentLocation: string;
age: string;
constructor() {
this.id = '';
this.email = '';
this.username = '';
this.password = '';
this.currentLocation = '';
this.age = '';
}
}
i also add the update service call into my service file
// profile.service.ts
updateProfile(user: User, userId: string) {
return this.http.put(environment.backendUrl + '/api/user/' + userId, user);
}
then i can call the service function from my component where i want to update my user
// profile-page.component.ts
updateProfile(user: User) {
this.profileService.updateProfile(user, user.id).subscribe(response => {
console.log('update successfull');
});
}
i hope my code snippets are able to help you! let me know if anything still needs some clarification. sry if this isnt the most perfect answer as this is one of my first answers on SO :)
401 means Authentication error.
Is post working?
I have this ReactJS app connected by Axios to a backend in Node. I'm trying to update, and the payload is correct, but I have an awkward problem: it says I'm not sending the _id that I need to be updated. Here is my mongoose Schema, the request in Axios and the express backend method for it.
Axios request:
submit () {
let data = this.state.category
axios({
method: this.state.category._id ? 'put':'post',
url: `/category/${this.state.category._id || ''}`,
data: data
})
.then(res => {
let list = this.state.categoryList
list.push(res.data.category)
this.update({
alert: {
type: "success",
text: "Category updated"
},
categoryList: list
})
this.toggleLarge()
})
.catch(e => {
this.update({
category: {
errors: e.errors
},
alert: {
type: "danger",
text: "Error",
details: e
}
})
})
}
Mongoose Schema:
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
let Schema = mongoose.Schema;
let categorySchema = new Schema({
description: {
type: String,
unique: true,
required: [true, 'Category required']
}
});
categorySchema.methods.toJSON = function() {
let category = this;
let categoryObject = category.toObject();
return categoryObject;
}
categorySchema.plugin(uniqueValidator, { message: '{PATH} must be unique' });
module.exports = mongoose.model('Category', categorySchema);
Express method:
app.put('/category/:id', [verifyToken], (req, res) => {
let id = req.params.id;
Category.findByIdAndUpdate(id, req.body, { new: true, runValidators: true }, (err, categoryDB) => {
if (err) {
return res.status(400).json({
ok: false,
err
});
}
res.json({
ok: true,
category: categoryDB
});
})
});
Request payload:
{"description":"Saladitos","errors":{},"_id":"5e5940dd7c567e1891c32cda","__v":0}
And the response:
"Validation failed: _id: Cannot read property 'ownerDocument' of null, description: Cannot read property 'ownerDocument' of null"
This is the contract of findByIdAndUpdate:
A.findByIdAndUpdate(id, update, options, callback)
Your update object is req.body which contains _id. Am guessing that it will try to update _id as well, which should not happend.
Try to specify which columns you want to update
Model.findByIdAndUpdate(id, { description: req.body.description, ... }, options, callback)
Hope this helps.
I can see you might not change the ObjectId:_id into other names. But for people who had done that and see a similar problem, Check this. https://liuzhenglai.com/post/5dbd385f8dea5b6b578765d9
So you probably need to change your code into this
const id = request.params.id
Model.findByIdAndUpdate(
id,
{id:id ,description: req.body.description, ... },
{runValidators: true, context: 'query'},
callback
)
You don't have to say {id: id} just pass context is equal to query like this
{runValidators: true, context: 'query'}
I'm using validate in mongoose schema for unique name now face issue while someone updates that record It won't allow the user to update because already have an entry in a database.
I've schema & code like following.
Schema
let mongoose = require('mongoose'),
Schema = mongoose.Schema;
let pTSchema = mongoose.Schema({
type: {
type: String,
required: true,
validate: {
validator: function(v, cb) {
v = v.toLowerCase();
PT.aggregate([ // Query for validate type should be unique if already exist than through error.
{
$addFields:{
lowerCase: { $toLower: "$type" }
}
},
{
$match:{
lowerCase: v
}
}
], function(err,docs){
console.log(docs, v);
cb(docs.length == 0);
});
},
message: 'p already exists!'
}
}
});
module.exports = PT = mongoose.model('pt', pTSchema);
Insert New Record.
// Working as expected with validation
var newPT = new PT();
newPT.type = req.body.type;
newPT.save(function(err) {
if (err)
return res.status(400).send({ status: false, message: JSON.stringify(err) })
return req.res.status(200).send({status:true, data: newPT});
});
Update Records.
// While update single record it also executes schema validate and won't give permission to save.
// Please give me some suggestion on this.
PT.findOne({ _id : where }, function(err, responsePT) {
if (!PT) {
return res.status(500).send({ status: false, message: "ERROR" });
}
responsePT.type = req.body.type;
responsePT.save(function(err) {
if (err)
return res.status(400).send({ status: false, message: JSON.stringify(err) })
return req.res.status(200).send({status:true, data: responsePT});
});
});
At, Final I didn't get any solution so, I update my code with .pre('save', It's fixed my problem.
I ignore updated entry.