Update field within nested array using mongoose - node.js

I'm trying to update the subdocument within the array without success. The new data doesn't get saved.
Express:
router.put('/:id/:bookid', (req, res) => {
library.findOneAndUpdate(
{ "_id": req.params.id, "books._id": req.params.bookid},
{
"$set": {
"title.$": 'new title'
}
}
});
LibraryScema:
const LibarySchema = new Library({
Name: {
type: String,
required: false
},
books: [BookSchema]
});
bookScema:
const BookSchema = new Schema({
title: {
type: String,
required: false
},
Chapters: [
{
chapterTitle: {
type: String,
required: false
}
}
]
});
I only aim to update the sub-document, not parent- and sub-document at same time.

I had a similar issue. I believe there is something wrong with the $set when it comes to nested arrays (There was an entire issue thread on GitHub). This is how I solved my issue.
var p = req.params;
var b = req.body;
Account.findById(req.user._id, function (err, acc) {
if (err) {
console.log(err);
} else {
acc.websites.set(req.params._id, req.body.url); //This solved it for me
acc.save((err, webs) => {
if (err) {
console.log(err);
} else {
console.log('all good');
res.redirect('/websites');
}
});
}
});
I have a user with a nested array.
Try this code
router.put('/:id/:bookid', (req, res) => {
library.findById(
req.params.id, (err, obj) => {
if (err) console.log(err); // Debugging
obj.books.set(req.params.bookid, {
"title": 'new title',
'Chapters': 'your chapters array'
});
obj.save((err,obj)=>{
if(err) console.log(err); // Debugging
else {
console.log(obj); // See if the saved object is what expected;
res.redirect('...') // Do smth here
}
})
})
});
Let me know if it works, and I'll add explanation.
Explanation: You start by finding the right object (library in this case), then you find the correct object in the array called books.
Using .set you set the whole object to the new state. You'll need to take the data that's not changing from a previous instance of the library object.
I believe this way will overwrite and remove any data that's not passed into the .set() method. And then you save() the changed.

Related

mongo DB always update first document

I have a posts collection that has array of likes.I want to push object into likes array if user have not liked and pull if user has liked the post.I test my API but it always update first document of collection though I provided postId of other document.
schema.js
likes: [
{
userId: String,
isNotified: {
type: Boolean,
default: false,
},
email: String,
types: String,
},
],
API
router.post("/like", (req, res) => {
postModel.find(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
(err, doc) => {
// console.log(doc)
if (!doc.length) {
postModel.updateOne(
{ "_Id": req.body.postId,},
{
$push: {
likes: {
userId: req.body.userId,
email: req.body.email,
// types: req.body.types,
},
},
},
(err, doc) => {
res.send("like");
}
);
} else {
// console.log("pull")
postModel.find(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
(err, doc) => {
doc.map((e) => {
e.likes.map((x) => {
if (x.userId == req.body.userId) {
postModel.updateOne(
{
"_Id": req.body.postId,
"likes.userId": req.body.userId,
},
{
$pull: {
likes: {
userId: req.body.userId,
email:req.body.email
},
},
},
(err, doc) => {
res.send("unlike");
}
);
}
});
});
}
);
}
// res.send(doc);
}
);
// });
});
postman request
{
"email":"mahima#gmail.com",
"types":"like",
"postId":"6312c2d1842444a707b6902f",
"userId":"631452d0e1c2acf0be28ce43"
}
How to fix this,suggest an advice.Thanks in advance.
I'm not sure if I undrestand the logic, but here are couple of things that I think you can improve:
You are using find method to get a single document, you should use findOne method which return a single document (if exists) and not an array of documents. But in general when you have the _id value of a document, it's better to just use findById method which is much faster.
When you find a document, you can just modify it and call it's save method to write your changes to the database, there is no need to use updateOne. (please note that partital update has many advantages but in your case they don't seem necessary, you can read about it online.)
your API code can be something like this:
router.post("/like", (req, res) => {
const postId = req.body.postId
const userId = req.body.userId
postModel.findById(postId) // get the post
.then(post => {
if (post) { // check if post exists
// check if user has already liked the post
if (post.likes.find(like => like.userId == userId)){
// user has already liked the post, so we want to
// remove it from likes (unlike the post).
// I know this is not the best way to remove an item
// from an array, but it's easy to understand and it
// also removes all duplications (just in case).
post.likes = post.likes.filter(like => like.userId != userId)
// save the modified post document and return
return post.save(_ => {
// send success message to client
res.send("unlike")
})
} else {
// user has not liked the post, so we want to add a
// like object to post's likes array
post.likes.push({
userId: userId,
email: req.body.email // you can other properties here
})
// save the modified post document and return
return post.save(_ => {
// send success message to client
res.send("like")
})
}
} else { // in case post doesn't exist
res.status(404).send("post not found.")
}
})
.catch(err => {
// you can handle errors here
console.log(err.message)
res.send("an error occurred")
})
})
I didn't run the code, but it should work.

How to use findByIdAndUpdate on mongodb?

I am a noobie in coding and I am having an issue with how to use properly MongoDB. I have a parent object classroom containing an array of objects - comments. I am trying to update the content of 1 selected comment.
originally I updated the state of the whole "classroom" in the react and passed all the data and $set {req.body} in findByIdAndUpdate.
I want to achieve the same result if I only pass to my axios request classId, commentId and comment data and not whole classroom / all comments
I tried to filter selected comment out of the array of comments and concat updated comment, but that did not work. Clearly, I have any idea what is going on and docs don't make it any easier for me to understand.
my classroom schema:
var ClassroomSchema = new Schema({
title: String,
teacher: String,
info: String,
image_url: String,
comments: [Comment.schema]
});
comment schema:
var CommentSchema = new Schema()
CommentSchema.add({
content: String,
comments: [CommentSchema],
created_at: {
type: Date,
default: Date.now
}
});
original solution:
function update(req, res){
Comment.findById(req.params.comment_id, function(err, comment) {
if(err) res.send(err)
comment.content = req.body.content;
comment.save();
console.log(req.body.comments)
Classroom.findByIdAndUpdate(req.params.classroom_id,
{$set: req.body}, function(err, classroom){
if (err) {
console.log(err);
res.send(err);
} else {
commentToUpdate = req.body.commentData;
res.json(classroom);
}
});
});
}
my current failing atempt:
function update(req, res){
console.log('update => req.body: ', req.body);
console.log('req.params', req.params)
Comment.findById(req.params.comment_id, function(err, comment) {
if(err) res.send(err)
comment.content = req.body.content;
comment.save();
console.log('comment: ', comment);
Classroom.findById(req.params.classroom_id, function(err, classroom) {
console.log('CLASSROOM findByIdAndUpdate classroom: ', classroom)
// console.log('reg.body: ', req.body)
if (err) {
console.warn('Error updating comment', err);
res.send(err);
} else {
// commentToUpdate = req.body.commentData;
old_comments = classroom.comments;
console.log('comments: ', old_comments);
Classroom.findByIdAndUpdate(req.params.classroom_id,
{$set:
{ comments: old_comments.filter(comt._id !== comment._id).concat(comment)}
}, function(err, updatedClassroom) {
if (err) {
console.warn(err);
} else {
res.json(updatedClassroom);
}
});
}
});
});
}
haven't tested, but try this.
function update(req, res) {
Classroom.update(
{ _id: req.params.classroom_id, "comments._id": req.params.comment_id },
{ $set: { "comments.$.content": req.body.content } },
function(err) {
..
}
);
}

Mongoose document.populate() is not working

I am using a pretty simple Node/Mongo/Express setup and am trying to populate referenced documents. Consider my schemas for "Courses" which contain "Weeks":
// define the schema for our user model
var courseSchema = mongoose.Schema({
teachers : { type: [String], required: true },
description : { type: String },
previous_course : { type: Schema.Types.ObjectId, ref: 'Course'},
next_course : { type: Schema.Types.ObjectId, ref: 'Course'},
weeks : { type: [Schema.Types.ObjectId], ref: 'Week'},
title : { type: String }
});
// create the model for Course and expose it to our app
module.exports = mongoose.model('Course', courseSchema);
I specifically want to populate my array of weeks (though when I changed the schema to be a single week, populate() still didn't work).
Here is my schema for a Week (which a Course has multiple of):
var weekSchema = mongoose.Schema({
ordinal_number : { type: Number, required: true },
description : { type: String },
course : { type: Schema.Types.ObjectId, ref: 'Course', required: true},
title : { type: String }
});
// create the model for Week and expose it to our app
module.exports = mongoose.model('Week', weekSchema);
Here is my controller where I am trying to populate the array of weeks inside of a course. I have followed this documentation:
// Get a single course
exports.show = function(req, res) {
// look up the course for the given id
Course.findById(req.params.id, function (err, course) {
// error checks
if (err) { return res.status(500).json({ error: err }); }
if (!course) { return res.sendStatus(404); }
// my code works until here, I get a valid course which in my DB has weeks (I can confirm in my DB and I can console.log the referenced _id(s))
// populate the document, return it
course.populate('weeks', function(err, course){
// NOTE when this object is returned, the array of weeks is empty
return res.status(200).json(course);
});
};
};
I find it strange that if I remove the .populate() portion from the code, I get the correct array of _ids back. But when I add the .populate() the returned array is suddenly empty. I am very confused!
I have also tried Model population (from: http://mongoosejs.com/docs/api.html#model_Model.populate) but I get the same results.
Thanks for any advice to get my population to work!
below should return course with populated weeks array
exports.show = function(req, res) {
// look up the course for the given id
Course.findById(req.params.id)
.populate({
path:"weeks",
model:"Week"
})
.exec(function (err, course) {
console.log(course);
});
};
### update: you can populate from instance also ###
Course.findById(req.params.id, function (err, course) {
// error checks
if (err) { return res.status(500).json({ error: err }); }
if (!course) { return res.sendStatus(404); }
// populate the document, return it
Course.populate(course, { path:"weeks", model:"Weeks" }, function(err, course){
console.log(course);
});
});
### Update2: Perhaps even more cleanly, this worked: ###
Course.findById(req.params.id, function (err, course) {
// error checks
if (err) { return res.status(500).json({ error: err }); }
if (!course) { return res.sendStatus(404); }
// populate the document, return it
console.log(course);
}).populate(course, { path:"weeks", model:"Weeks" });
here it seems like you are using course.populate() instead of Course.populate()
Use this code instead of yours,I change only one single word course.populate() to Course.populate()
In your case "course" is instance but you need to use Course(Model)
Course.findById(req.params.id, function (err, course) {
if (err) { return res.status(500).json({ error: err }); }
if (!course) { return res.sendStatus(404); }
// Guys in some case below three-line does not work in that case you must comment these lines and uncomments the last three-line
Course.populate('weeks', function(err, course){
return res.status(200).json(course);
});
// Course.populate({ path:"weeks", model:"Weeks" }, function(err, course){
// return res.status(200).json(course);
// });
};

Update a record where _id = :id with Mongoose

I'm trying to update an existing record with Mongoose. The insert is OK but not the update.
Here is my snippet:
app.post('/submit', function(req, res) {
var my_visit = new models.visits({
date: req.body.visit_date,
type: req.body.visit_type,
agency: req.body.visit_agency,
city: req.body.visit_city,
url: req.body.visit_url,
note: req.body.visit_note
});
// INSERT
if(req.body.id == 0) {
my_visit.save(function(err) {
if(err) { throw err; }
console.log('added visit');
res.redirect('/');
});
} else { // UPDATE
var upsertData = my_visit.toObject();
console.log(req.body.id); // OK
models.visits.update({ _id: req.body.id }, upsertData, { multi: false }, function(err) {
if(err) { throw err; }
console.log('updated visit: '+ req.body.id);
res.redirect('/');
});
}
})
The response is Mod on _id is not allowed.
I just want to update the line such as WHERE id = id in MySQL. I didn't find the right syntax.
According to this question and this other one, the Mod on _id is not allowed occurs when one tries to update an object based on its id without deleting it first.
I also found this github issue which tries to explain a solution. It explicitly states:
Be careful to not use an existing model instance for the update clause
(this won't work and can cause weird behavior like infinite loops).
Also, ensure that the update clause does not have an _id property,
which causes Mongo to return a "Mod on _id not allowed" error.
The solution, it seems, is to do the following:
var upsertData = my_visit.toObject();
console.log(req.body.id); // OK
delete upsertData._id;
models.visits.update({ _id: req.body.id }, upsertData, { multi: false }, function(err) {
if(err) { throw err; }
//...
}
On a side note, you can probably rewrite your route to do both the create and update without the if-else clause. update() takes an extra option upsert, which, according to the docs:
upsert (boolean) whether to create the doc if it doesn't match (false)
Here is my solution:
routes/router.js
router.patch('/user/:id', userController.updateUser)
exports.updateUser = async(req, res) => {
const updates = Object.keys(req.body)
const allowedUpdates = ['name', 'email', 'password', 'age']
const isValidOperation = updates.every((update) => allowedUpdates.includes(update))
if (!isValidOperation) {
return res.status(400).send('Invalid updates!')
}
try {
const user = await UserModel.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
if (!user) {
return res.status(404).send()
}
res.status(201).send(user)
} catch (error) {
res.status(400).send(error)
}
}

Mongoose delete array element in document and save

I have an array in my model document. I would like to delete elements in that array based on a key I provide and then update MongoDB. Is this possible?
Here's my attempt:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var favorite = new Schema({
cn: String,
favorites: Array
});
module.exports = mongoose.model('Favorite', favorite, 'favorite');
exports.deleteFavorite = function (req, res, next) {
if (req.params.callback !== null) {
res.contentType = 'application/javascript';
}
Favorite.find({cn: req.params.name}, function (error, docs) {
var records = {'records': docs};
if (error) {
process.stderr.write(error);
}
docs[0]._doc.favorites.remove({uid: req.params.deleteUid});
Favorite.save(function (error, docs) {
var records = {'records': docs};
if (error) {
process.stderr.write(error);
}
res.send(records);
return next();
});
});
};
So far it finds the document but the remove nor save works.
You can also do the update directly in MongoDB without having to load the document and modify it using code. Use the $pull or $pullAll operators to remove the item from the array :
Favorite.updateOne({ cn: req.params.name }, {
$pullAll: {
favorites: req.params.deleteUid,
},
});
To remove objects from array then
Favorite.updateOne({ cn: req.params.name }, {
$pullAll: {
favorites: [{_id: req.params.deleteUid}],
},
});
(you can also use updateMany for multiple documents)
http://docs.mongodb.org/manual/reference/operator/update/pullAll/
The checked answer does work but officially in MongooseJS latest, you should use pull.
doc.subdocs.push({ _id: 4815162342 }) // added
doc.subdocs.pull({ _id: 4815162342 }) // removed
https://mongoosejs.com/docs/api.html#mongoosearray_MongooseArray-pull
I was just looking that up too.
See Daniel's answer for the correct answer. Much better.
Answers above are shown how to remove an array and here is how to pull an object from an array.
Reference: https://docs.mongodb.com/manual/reference/operator/update/pull/
db.survey.update( // select your doc in moongo
{ }, // your query, usually match by _id
{ $pull: { results: { $elemMatch: { score: 8 , item: "B" } } } }, // item(s) to match from array you want to pull/remove
{ multi: true } // set this to true if you want to remove multiple elements.
)
Since favorites is an array, you just need to splice it off and save the document.
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var favorite = new Schema({
cn: String,
favorites: Array
});
module.exports = mongoose.model('Favorite', favorite);
exports.deleteFavorite = function (req, res, next) {
if (req.params.callback !== null) {
res.contentType = 'application/javascript';
}
// Changed to findOne instead of find to get a single document with the favorites.
Favorite.findOne({cn: req.params.name}, function (error, doc) {
if (error) {
res.send(null, 500);
} else if (doc) {
var records = {'records': doc};
// find the delete uid in the favorites array
var idx = doc.favorites ? doc.favorites.indexOf(req.params.deleteUid) : -1;
// is it valid?
if (idx !== -1) {
// remove it from the array.
doc.favorites.splice(idx, 1);
// save the doc
doc.save(function(error) {
if (error) {
console.log(error);
res.send(null, 500);
} else {
// send the records
res.send(records);
}
});
// stop here, otherwise 404
return;
}
}
// send 404 not found
res.send(null, 404);
});
};
This is working for me and really very helpful.
SubCategory.update({ _id: { $in:
arrOfSubCategory.map(function (obj) {
return mongoose.Types.ObjectId(obj);
})
} },
{
$pull: {
coupon: couponId,
}
}, { multi: true }, function (err, numberAffected) {
if(err) {
return callback({
error:err
})
}
})
});
I have a model which name is SubCategory and I want to remove Coupon from this category Array. I have an array of categories so I have used arrOfSubCategory. So I fetch each array of object from this array with map function with the help of $in operator.
keywords = [1,2,3,4];
doc.array.pull(1) //this remove one item from a array
doc.array.pull(...keywords) // this remove multiple items in a array
if you want to use ... you should call 'use strict'; at the top of your js file; :)
I used this format for my project and it's worked
router.delete('/dashboard/participant/:id', async (req, res, next) => {
try {
const participant = await Participant.findByIdAndDelete({ _id: req.params.id });
// { $pull: { templates: { _id: templateid } } },
const event = await Event.findOneAndUpdate({ participants: participant._id }, { $pull: { participants: participant._id } }, { new: true });
res.status(200).json({ request: 'Deleted', participant, event });
} catch (error) {
res.json(error)
}
});
Favorite.update({ cn: req.params.name }, { "$pull": { "favorites": { "_id": favoriteId } }}, { safe: true, multi:true }, function(err, obj) {
//do something smart
});

Resources