I've been trying to build my first solo CRUD app (without following a tutorial) and came across something a little weird which I can't wrap my head around. Apologies if it's rather obvious, but I couldn't find anything on here about it.
I added my delete route for one of my models and used the following code:
// delete list
router.delete("/lists/:id", function(req, res){
// find list by ID and remove it
List.findOneAndRemove(req.params.id, function(err, deletedList){
if(err) {
console.log("ERROR DELETING LIST");
console.log(err);
res.redirect("/lists");
} else {
// if list is removed successfully, remove each item
console.log(deletedList);
Item.remove({_id: {$in: deletedList.items}}, function(err, deletedItems){
if(err) {
console.log("ERROR DELETING ITEMS");
console.log(err);
} else {
console.log("SUCCESSFULLY DELETED LIST & ALL ITEMS");
res.redirect("/lists");
}
});
}
});
});
Now, it deletes things from the database, however it doesn't delete the right thing. I have a button on my web page which submits a form. The form's action is generated based on the page content where I insert the ID of the model into the action using EJS. When I submit the form to delete the model and it's referenced items, it ends up deleting the first document in the database, even though (to my understanding), I've specified that only documents which match the provided ID should be deleted.
I did fix it, by replacing
List.findOneAndRemove(req.params.id, function(err, deletedList){
if(err) {
console.log("ERROR DELETING LIST");
console.log(err);
res.redirect("/lists");
} else {
with
List.findOneAndRemove({_id: {$in: req.params.id}}, function(err, deletedList){
if(err) {
console.log("ERROR DELETING LIST");
console.log(err);
res.redirect("/lists");
} else {
though I'm not completely sure on why this works and req.params.id doesn't. I was just hoping for some clarification in case I come across something similar in the future.
I believe it is deleting the first item in your array because you are using "FindOneAndRemove" - as is suggested in the name, this will limit the match to a single document. And in your first version of the code:
List.findOneAndRemove(req.params.id, function(err, deletedList){
You aren't specifying your _id field, so i believe it is matching everything, and is only removing a single document due to the method you are using. Where as in the second one, you are
List.findOneAndRemove({_id: {$in: req.params.id}}, function(err, deletedList){
Here's a possible reason I can point. When you pass req.params.id to findOneAndRemove, since JS is not a strongly typed language and you are not specifically typing it, Mongoose isn't sure about what to delete.
The first parameter to findOneAndRemove is the filter object and Mongoose is not able to find the right filtering since the data type that is passed here is a string.
I think then Mongoose consider an empty filter and as usual, empty filter deletes the first item.
One try you can do is to explicitly type the _id, say like
var id = new mongoose.mongo.ObjectID(req.params.id)
and pass this id to the findOneAndRemove() function.
Hope this helps.
Related
I am new to JS and I am utilizing the MEAN stack to create a place where students can add classes to their user profile. I already have a session store in my database, so "req.user" will return the currently logged in user information and specifically "req.user.id" will return the currently logged in user's id. Also, I have figured out how to search a course in my database from my application. Ultimately, my goal is that when the user makes the post request to search in the database, I also want those "values" to be pushed into the classes "key". I have provided two options, both of which do not add the respective strings to the database. Thank you for any help!
Portion of Search.JS Option #1
router.post('/', ensureAuthenticated, function (req,res,next) {
var query = {course: req.body.coursename};
db.collection('courses').find(query).toArray()
.then(db.collection('DefaultUser').update({_id: req.user.id}, {$push: {classes: req.body.coursename}}));
res.render('search', {title: 'search'})
});
Portion of Search.JS Option #2
router.post('/', ensureAuthenticated, function(req,res,next) {
var query = {course: req.body.coursename};
db.collection('courses').find(query).toArray((err, results) => {
if (err) {
throw err;
} else {
db.collection('DefaultUser').updateOne({_id: '5c17161d3347e79410ff29ba'}, {
$push: {
classes: req.body.coursename
}
})
console.log(results)
res.render('search', {courses: results, title: 'search'})
}
})
});
Some tips may help:
req.body will hold nothing if you forget to use app.use(express.urlencoded()) to parse the posted data.
You may use ObjectId('<string>') (in option #2) for finding and updating queries, not just the string, because mongodb stores _id as ObjectId type by default.
You may use $addToSet instead of $push modifier to add a unique element to an array, or you may get one student subscribed with two more same class, if he repeatedly submit the form.
In your code, you find in the courses collection first and then update, but since you did nothing with the find result, it is not necessary (empty result does not throw any error). Checking the data is valid is good practice, if you would like to do so, in both options, you need to use findOne instead of find to make sure the course is existed, and .then(course => {/* check if the course is null and then throws an error */} ).
I don't have the full code of your project so I can only guess there may be the problems listed above, wish you good luck!
I'm new to NodeJS and Mongoose and this might be a duplicate question, so don't reply back negatively please. I tried finding a solution but failed to fix this error.
Basically, I get this error when I try to update the database values. The first update I perform works perfectly but from the second one onward, I get this error. The values update but it updates several fields at once giving this error.
Here's my code: (and I have added my github link to the project):
User.js (model)
local: {
...,
education: [{
id: String,
degree: String,
institute: String,
dates: String,
description: String
}],
...
}
router.js
app.put('/put_education/:id', function(req, res) {
var row_id = req.params.id; //get education id from table row
//get the current logged in user
User.findById(req.user._id, function (err, doc) {
if (err) {
console.log('no entry found');
}
//match the table row id with the id in users education model
doc.local.education.forEach(function (education, index) {
console.log(education.id + " " + row_id);
//if rows match, replace the database document with values from client
if(education.id === row_id){
doc.local.education[index] = req.body;
doc.save(function (err) {
if (err) {
console.log(err);
} else {
res.send("Success");
}
});
}
});
});
});
I added a console.log to see the loop operates, the image below shows its fine for the first iteration but acts weirdly for the next ones:
I was thinking of breaking the loop after the id's match but the foreach loop doesnt have a break function, I changed it to a normal for loop but it still gives me the same error. So I dont think breaking the loop is an answer..
Edit: Added image of my website to show what happens after updating (duplicates rows)
Github: https://github.coventry.ac.uk/salmanfazal01/304CEM-Back-End
if break out of an iteration is your issue, then why not use a simple for loop with break statement.
let eduArray = doc.local.education;
for(i=0;i<eduArray.length;i++) {
if(eduArray[i]["id"] == row_id) {
// do your logic
break;
}
}
becuase you are updating the education first time with no problem, then the problem appears afterwards, I suspect that you update the education wrongly, look into this line of code you wrote:
doc.local.education[index] = req.body;
here I see you are assigning whatever inside the request body to education, but have you checked what data is actually inside the req.body?
try to log the req.body to see what you actually assigning to the education.
I have two models defined like this:
var OrganizationSchema = new mongoose.Schema({
users: [mongoose.Schema.Types.ObjectId]
});
and
var UserSchema = new mongoose.Schema({
organizations: [mongoose.Schema.types.ObjectId]
});
When a user want to join an organization I need to update both the organization collection and the user collection. I want to know what is the best way to achieve this ? Is it worth considering the case when one update request fail ? I'm currently doing something like this (where organization is a collection instance of the model Organization):
User.findByIdAndUpdate(req.userSession.userId, { $push: { organizations: organization.id } }, function (err){
if (err)
{
// server error
console.log(err);
}
else
{
organization.users.push(req.userSession.userId);
organization.save(function (err){
if (err)
{
// server error we need to cancel
User.findByIdAndUpdate(req.userSession.userId, { $pull: { organizations: organization.id } }, function (err){
if (err)
{
// we got a problem one collection updated and not the other one !!
console.log(err);
}
});
}
else
{
// success
}
});
}
});
The problem is: if my second update method fail I will end up with one collection updated and not the other ? Is there a way to make sure they are both updated ?
Well firstly, I would stay clear of that design. I would either reference or embed user in organisations and the other way around, not both of them at same time, so I wouldn't have problems like this(which happens every-time you duplicate data).
MongoDB doesn't have support for simultaneous updates, or transactions. So you are left to manage this in your code.
So yes, if the second update fails, then as you wrote your code you have to rollback, and if the rollback fails, you have to retry till it succeeds(though with exponential backoff probably). Keep in mind that might intefer with other requests(another user tries to save the same thing simultaneously). To handle that you have to give a unique to each entry in the array.
Service get array of users' names, check every name if user with this name exists(if not then create user) and return ids of users with such names. I don't know how to do it because search and save are async.
Following code is for query the collection to get a document based on id .if its found it returns the doc other wise it can inseret the doc for that we used {upsert:true}.this is basic code sample follow mongoose docs http://mongoosejs.com/docs/api.html
Model.findOneAndUpdate({_id:id},{upsert:true}, function(err, doc){
if (err) {
console.log('if error occurs');
} else {
console.log('doc',doc)
}
});
I did it using async library for node js.
I'm trying to add new function allowing to delete post for authorized users, so they can remove posts only they created. It works only with one statement, so I can allow them to delete all of their (only their) posts or delete selected posts, but of all users.
Heres code:
article_collection.remove({ _id: article_collection.db.bson_serializer.ObjectID.createFromHexString(id) }, {authoro: user}, function(error, result) {
When I add $and function, it does nothing, in the case posted above, it allows all users to delete any post.
I'm guessing I need to convert passed id of post to hex? but how?
Both query conditions must be combined into a single object:
article_collection.remove({_id: id, authoro: user}, function(error, result) { ...