Am I doing this correctly in the backend API? How would you delete an object inside an array within a parent array in the backend? I first found the main parent array index and then I found the object from tasks array using .tasks[index]. The question is how would I delete this in node? Tutorials I found uses req.params.id to delete an item but mine is more complicated.
exports.deleteTaskItem = async (req, res) => {
const taskindex = req.params.id;
const index = req.params.index;
try {
const taskfound = await Task.findById(taskindex);
const taskfounditem = await taskfound.tasks[index];
//code to type here
res.status(204).json({
status: "success",
data: null
});
} catch (err) {
res.status(404).json({
status: "fail",
message: err
});
}
};
I believe this piece of documentation would interest you:
https://docs.mongodb.com/manual/reference/operator/update-array/
And to specify, I believe you want to use the $pull operator.
Something like this:
const {id, index} = req.params;
await Task.findByIdAndUpdate(id,{
$pull: {
tasks: { _id: index }
}
});
(Disclaimer: I did not test this out this time, sorry. But it should be close.)
edit: Now when I reread the question I notice that you want to use the index. Personally I think it'd be easier to just add ids since you get that automatically if you use a sub-document. But if you insist on using index, maybe this answer can help:
https://stackoverflow.com/a/4970050/1497533
edit again:
It seems this helped getting a working solution, so I'll copy it in from comments:
taskfound.tasks.splice(taskindex, 1);
taskfound.markModified('tasks');
await taskfound.save();
Related
I have one schema which contains an array of references to another schema (among other fields):
const RecipeIngredient = new Schema({
ingredientId: { // store id ref so I can populate later
type: Schema.Types.ObjectId,
ref: 'ingredients',
required: true
},
// there are a couple other fields but not relevant here
});
const Recipe = new Schema({
ingredients: [RecipeIngredient]
});
I'm trying to write a route which will first find a recipe by _id, populate the ingredients array (already have this working), and finally iterate over each ingredient in that array.
router.get('/:recipeId/testing', async (req, res) => {
const { recipeId } = req.params
let recipe = await Recipe
.findById(recipeId)
.populate({
path: 'ingredients.ingredientId',
model: 'Ingredient',
select: '_id ......' //I'm selecting other fields too
})
.lean()
.exec();
if (recipe) {
const { ingredients } = recipe;
const newIngredients = [];
await ingredients.forEach(async (ingr) => {
// here I'd like to be able to run a new query
// and append the result to an array outside of the forEach
// I do need information about the ingr in order to run the new query
newIngredients.push(resultOfNewQuery);
});
return res.json(newIngredients)
};
return res.status(404).json({ noRecipeFound: 'No recipe found.'});
})
I've tried approaching this in a few different ways, and the closest I've gotten was executing the new query within each iteration, but because the query is async, I return the response before I've actually collected the documents from the inner query.
I also attempted to use .cursor() in the initial query, but that won't work for me because I do need to access the ingredients field on the recipe once it is resolved before I can iterate and run the new queries.
Any ideas would be appreciated! I'm definitely opening to restructuring this whole route if my approach is not ideal.
I was able to make this work by using a for loop:
const newIngredients = [];
for (let idx = 0; idx < ingredients.length; idx++) {
const { fieldsImInterestedIn } = ingredients[idx];
const matchingIngredients = await Ingredient
.find(fieldsImInterestedIn)
.lean()
.exec()
.catch(err => res.status(404).json({ noIngredientsFound: 'No ingredients found' }));
newIngredients.push(ingredientsToChooseFrom[randomIndex]);
};
return res.json(newIngredients);
still a little perplexed as to why this was able to work while forEach wasn't, but I'll happily move on...
I am using .pull to remove a record from an array in mongo db and it works fine, but a comment I read somewhere on stack overflow (can't find it again to post the link) is bothering me in that it commented that it was bad to use .save instead of using .findByIdAndUpdate or .updateOne
I just wanted to find out if this is accurate or subjective.
This is how I am doing it currently. I check if the product with that id actually exists, and if so I pull that record from the array.
exports.deleteImg = (req, res, next) => {
const productId = req.params.productId;
const imgId = req.params.imgId;
Product.findById(productId)
.then(product => {
if (!product) {
return res.status(500).json({ message: "Product not found" });
} else {
product.images.pull(imgId);
product.save()
.then(response => {
return res.status(200).json( { message: 'Image deleted'} );
})
}
})
.catch(err => {
console.log(err);
});
};
I think what they were saying though was it should rather be done something like this (an example I found after a google)
users.findByIdAndUpdate(userID,
{$pull: {friends: friend}},
{safe: true, upsert: true},
function(err, doc) {
if(err){
console.log(err);
}else{
//do stuff
}
}
);
The main difference is that when you use findById and save, you first get the object from MongoDB and then update whatever you want to and then save. This is ok when you don't need to worry about parallelism or multiple queries to the same object.
findByIdAndUpdate is atomic. When you execute this multiple times, MongoDB will take care of the parallelism for you. Folllowing your example, if two requests are made at the same time on the same object, passing { $pull: { friends: friendId } }, the result will be the expected: only one friend will be pulled from the array.
But let's say you've a counter on the object, like friendsTotal with starting value at 0. And you hit the endpoint that must increase the counter by one twice, for the same object.
If you use findById, then increase and then save, you'd have some problems because you are setting the whole value. So, you first get the object, increase to 1, and update. But the other request did the same. You'll end up with friendsTotal = 1.
With findByIdAndUpdate you could use { $inc: { friendsTotal: 1 } }. So, even if you execute this query twice, on the same time, on the same object, you would end up with friendsTotal = 2, because MongoDB use these update operators to better handle parallelism, data locking and more.
See more about $inc here: https://docs.mongodb.com/manual/reference/operator/update/inc/
Below is a code that deletes a sub document inside roomTypes field and it works fine. The problem is that i want to reuse the code instead of writting one. This because apart from having a field roomTypes with sub documents i also have other fields with sub documents. so i would like to write one code that i can use to update them.
So under the field that is being pulled is there way i can use something like a template string the way i have done for { _id: ${req.params.position} } so that i can update a field based on what is coming in the request body. In short i do not want to hard code the roomTypes field. I want it to be dynamic based on what is coming from the request body. Please help on this
exports.deleteRoomType = catchAsync(async (req, res, next) => {
const reqHostel = await Hostel.findByIdAndUpdate(req.params.id, {
$pull: {
roomTypes: { _id: `${req.params.position}` }
}
});
// process.exit();
res.status(204).json({
status: 'success',
data: null
});
});
You can pass another paramer fieldName and build your update expression based on that field's value:
const reqHostel = await Hostel.findByIdAndUpdate(req.params.id, {
$pull: {
[req.params.fieldName]: { _id: `${req.params.position}` }
}
})
I am using .pull to remove a record from an array in mongo db and it works fine, but a comment I read somewhere on stack overflow (can't find it again to post the link) is bothering me in that it commented that it was bad to use .save instead of using .findByIdAndUpdate or .updateOne
I just wanted to find out if this is accurate or subjective.
This is how I am doing it currently. I check if the product with that id actually exists, and if so I pull that record from the array.
exports.deleteImg = (req, res, next) => {
const productId = req.params.productId;
const imgId = req.params.imgId;
Product.findById(productId)
.then(product => {
if (!product) {
return res.status(500).json({ message: "Product not found" });
} else {
product.images.pull(imgId);
product.save()
.then(response => {
return res.status(200).json( { message: 'Image deleted'} );
})
}
})
.catch(err => {
console.log(err);
});
};
I think what they were saying though was it should rather be done something like this (an example I found after a google)
users.findByIdAndUpdate(userID,
{$pull: {friends: friend}},
{safe: true, upsert: true},
function(err, doc) {
if(err){
console.log(err);
}else{
//do stuff
}
}
);
The main difference is that when you use findById and save, you first get the object from MongoDB and then update whatever you want to and then save. This is ok when you don't need to worry about parallelism or multiple queries to the same object.
findByIdAndUpdate is atomic. When you execute this multiple times, MongoDB will take care of the parallelism for you. Folllowing your example, if two requests are made at the same time on the same object, passing { $pull: { friends: friendId } }, the result will be the expected: only one friend will be pulled from the array.
But let's say you've a counter on the object, like friendsTotal with starting value at 0. And you hit the endpoint that must increase the counter by one twice, for the same object.
If you use findById, then increase and then save, you'd have some problems because you are setting the whole value. So, you first get the object, increase to 1, and update. But the other request did the same. You'll end up with friendsTotal = 1.
With findByIdAndUpdate you could use { $inc: { friendsTotal: 1 } }. So, even if you execute this query twice, on the same time, on the same object, you would end up with friendsTotal = 2, because MongoDB use these update operators to better handle parallelism, data locking and more.
See more about $inc here: https://docs.mongodb.com/manual/reference/operator/update/inc/
In my project, I would like to implement a comment section which consists of list of comments.
const myschema= mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
//other fields
comments : [comment]
},{collection : 'TABLE_NAME'} );
const comment= mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
commentid: String, // id consists of uuid1()
senderid: Number, //user's id
body: String, // useful information
parent: String,// parent id consists of uuid1()
children: [] // I couldn't add children = [comment], it gives me an error,
//instead, I make it empty and will fill it when a comment comes
})
In the request tab, I will receive a JSON in the following format:
{
"userid": "NUMBERID HERE",
"comment": "COMMENT BODY",
"parent" : "Comment's parent id"
}
I would like to add a comment which can be a child of another comment. How can I search and find the appropriate position?
If there are no parent in JSON body, I'm doing this:
// import comment somewhere at the beginning
.then(doc =>{
var newc= new comment();
newc.cid = uuidv1();
newc.sender = req.body.userid;
newc.body = req.body.comment;
newc.parent = "";
newc.children = "";
doc.comments.push(newc);
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
res.status(500).json({error: err});
})
}
I have no idea how to find a comment that resides in a deep level
You cannot search for an array element or object property given an unspecified, arbitrarily-nested depth. It just isn't possible. You would need to instead handle this in the application layer. Denormalizing your data is fine in many cases, but arbitrary nesting depths isn't a recommended use case for data denormalization, especially since you can't index efficiently!
If you want a pure MongoDB solution, then you'll need a different document structure. I would recommend taking a look at the documentation, particularly the section concerning an array of ancestors, in order to properly model your data.
I have found a solution.
The manually traversing the comments and inserting in the right place works. However, it only works up to some level. In my case, I can insert a comment below comment and save it. I can also insert a 3rd deep level comment and see the JSON dump of the object that I have inserted or even wrap it in an HTTP response object and send to the client. But the database does not save this update.
What I did is, after inserting the comment in the correct place I added this code before saving to the database.
doc.markModified('comments'); // this is added
doc.save().then(/*prepare response*/);
Somehow the MongoDB or mongoose or javascript interpreter knows the document has been changed and save the updated version. If the markmodified part is not specified, probably the compiler thinks that the object has not been modified and skips it.
Hopefully, this helps people who encounter this issue.
This is my implementation
// find the document which comment is going to be inserted
MyDOC.findOne({'id' : req.params.id})
.exec()
.then(doc =>{
// if the comment has a parent, it should be inserted in nested
if(req.body.parent != null && req.body.parent != undefined)
{
// find correct position and insert
findComment(doc.comments, req.body);
doc.markModified('comments');
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
else
{
//Add comment to the root level
var comment= new Comment();
comment.cid = uuidv1();
comment.body = req.body.comment;
comment.parent = "";
doc.comments.push(comment);
doc.markModified('comments');
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
})
function findComment(comment, body)
{
comment.forEach(element => {
if(element.children.length != 0)
{
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
if(findComment(element.children, body, usr))
return true;
}
else
{
// this comment does not have children. If this comments id and the body's parent is equal, add it
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
return false;
}
});
}