Mongoose's findByIdAndUpdate not changing database's state - node.js

I have a strange problem. I want to update a document in my MongoDB with the mongoose.findByIdAndUpdate method, but it seems not to be working. The code is:
Device.findByIdAndUpdate(
req.params.id,
{ $set: { power: power } },
{ new: true },
(err, device) => { ... }
I get no error, but the device returned in the callback does not have the updated value. At first I thought maybe it was some sort of problem with the { new: true } option that tells mongoose to return the updated document, but then I checked the database, and the value there also has not been updated.
I also tried replacing findByIdAndUpdate with update function, but the results are the same - the db is not getting updated.
If it changes anything I use mongoose.update() function in other places and it works fine. I also tried the 'classical' way of updating the value here - meaning I used findOne function and then changed returned document's power field value and saved it and it also worked fine.
I will be really gratefull for any advice on fixing this. Thank you!

Related

Having trouble incrementing objects inside array in with moongose

So I've got an object that looks like this
{"_id":"5fb07ab6215679200cef0eb1","user":{"_id":"5fb07437538fcd2870e21a8e","email":"example#example.com","id":"5fb07437538fcd2870e21a8e"},"question":"question?","answers":[{"_id":"5fb07ab6215679200cef0eb2","answer":"Yes","votes":0}],"voters":[],"createdAt":"2020-11-15T00:47:50.156Z","updatedAt":"2020-11-15T00:47:50.156Z","__v":0,"id":"5fb07ab6215679200cef0eb1"}
and I'm trying to increase the votes variable by this function using findOneAndUpdate
export const castVote = async (id, answersid) =>
Poll.findOneAndUpdate(
{ id, 'answers._id': answersid },
{ $inc: { 'answers.$.votes': 1 } }
);
As far as i can see calling castVote("5fb07ab6215679200cef0eb1", "5fb07ab6215679200cef0eb2") works as in not crashing the server and not giving any errors back, but the votes variable in the answers object doesn't increase so something must be wrong. Is there something obvious I'm missing here?.
got it working by simply dropping the id field which i guess i enough since they're uniquely created

How to validate array length when using $push?

I'm trying to limit the amount of elements a user can add to an array field on one of my schemas. I'm currently adding the elements to the array using Schema.findOneAndUpdate(); with the $push operator.
The first thing I tried was the solution given by another answer here on StackOverflow, namely: https://stackoverflow.com/a/29418656/6502807
This solution adds a validate function to the fields in the schema definition. By setting runValidators to true, I did get the function to run with Schema.findOneAndUpdate(). It was at that moment, however, that I stumbled upon the next problem. At the end of the Validation chapter in the Mongoose docs it says:
Also, $push, $addToSet, $pull, and $pullAll validation does not run any validation on the array itself, only individual elements of the array.
So attempting to check for array length did not work when using $pull. It simply supplied the validation function with an empty array every time, regardless of its actual contents in the database.
Next thing I tried was to use a pre hook. This was without any success as well. For some reason it did not execute the hook, even with runValidators set to true. This is how I defined said hook:
Settings.pre('update', async function (next) {
if (this.messages.length > MAX_MESSAGES) {
throw new Error('Too many messages');
} else {
next();
}
});
EDIT: The reason the function did not fire was because I was using findOneAndUpdate instead of update this is fixed and the function now runs. The solution code above, however, does not work.
The schema with the array looks like this:
const Settings = new mongoose.Schema({
// A lot more fields not relevant to this question
messages: {
type: [{
type: String
}]
}
});
Another thing worth mentioning is that these update statements are used in conjunction with other options. I need the update statement to behave like an update or insert so my complete set of options looks like this:
{
runValidators: true,
setDefaultsOnInsert: true,
upsert: true,
new: true
}
When executing queries with the pre hook set like this, the array limit can be exceeded without any validation error being thrown.
At this point I'm wondering if there is any sensible way to do a max length check like this without having to do it myself outside of mongoose's abstraction layer.
I am using Mongoose 5.2.6 running on node v9.11.1 with MongoDB 4.0.0.
Any help is much appreciated!
Well if you are using latest version from mongodb and mongoose then you can use $expr operator
const udpate = await db.collection.update(
{ $expr: { $gt: [{"$size": "$messages" }, MAX_MESSAGES] }},
{ update }
)
You should be able to do that with the pre update hook. The thing is that that hook would not by default give you the update being mage so you can verify etc. You have to take it via this.getUpdate():
Settings.pre('update', async function (next) {
var preUpdate = this.getUpdate()
// now inside of the preUpdate you would have your update being made and should have the array in there on which you can check the length
});
To give you an idea in my test schema I had to do something like this on an update with a $set:
this.getUpdate().$set.books.length // gave me 2 which was correct etc
I also had no issues running and hitting the update hook at all. It looks super simple out of the mongoose docs:
AuthorSchema.pre('update', function(next) {
console.log('UPDATE hook fired!')
console.log(this.getUpdate())
next();
});

findOneAndUpdate works part of the time. MEAN stack

I'm working with the mean stack I'm trying to update the following object:
{
_id : "the id",
fields to be updated....
}
This is the function that does the updating:
function updateById(_id, update, opts){
var deferred = Q.defer();
var validId = new RegExp("^[0-9a-fA-F]{24}$");
if(!validId.test(_id)){
deferred.reject({error: 'invalid id'});
} else {
collection.findOneAndUpdate({"_id": new ObjectID(_id)}, update, opts)
.then(function(result){
deferred.resolve(result);
},
function(err){
deferred.reject(err);
});
}
return deferred.promise;
}
This works with some of my objects, but doesn't work with others.
This is what is returned when it fails to update:
{
ok: 1,
value:null
}
When the function is successful in updating the object it returns this:
{
lastErrorObject: {}
ok: 1
value: {}
}
It seems like Mongo is unable to find the objects I'm trying to update when it fails. However, I can locate those objects within the Mongo shell using their _id.
Does anybody know why the driver would be behaving this way? Could my data have become corrupt?
Cheers!
I found the answer and now this question seems more ambiguous so I apologize if it was confusing.
The reason I was able to find some of the documents using ObjectID(_id) was because I had manually generated some _id fields using strings.
Now I feel like an idiot but, instead of deleting this question I decided to post the answer just in case someone is running into a similar issue. If you save an _id as a string querying the collection with the _id field changes.
querying collection with MongoDB generated _ids:
collection.findOneAndUpdate({"_id": new ObjectID(_id)}, update, opts)
querying collection with manually generated _ids:
collection.findOneAndUpdate({"_id": _id}, update, opts)
In the second example _id is a string.
Hope this helps someone!

Saving subdocuments with mongoose

I have this:
exports.deleteSlide = function(data,callback){
customers.findOne(data.query,{'files.$':1},function(err,data2){
if(data2){
console.log(data2.files[0]);
data2.files[0].slides.splice((data.slide-1),1);
data2.files[0].markModified('slides');
data2.save(function(err,product,numberAffected){
if(numberAffected==1){
console.log("manifest saved");
var back={success:true};
console.log(product.files[0]);
callback(back);
return;
}
});
}
});
}
I get the "manifest saved" message and a callback with success being true.
When I do the console.log when I first find the data, and compare it with the console.log after I save the data, it looks like what I expect. I don't get any errors.
However, when I look at the database after running this code, it looks like nothing was ever changed. The element that I should have deleted, still appears?
What's wrong here?
EDIT:
For my query, I do {'name':'some string','files.name':'some string'}, and if the object is found, I get an array of files with one object in it.
I guess this is a subdoc.
I've looked around and it says the rules for saving subdocs are different than saving the entire collection, or rather, the subdocs are only applied when the root object is saved.
I've been going around this by grabbing the entire root object, then I do loops to find the actual subdoc I that I want, and after I manipulate that, I save the whole object.
Can I avoid doing this?
I'd probably just switch to using native drivers for this query as it is much simpler. (For that matter, I recently dropped mongoose on my primary project and am happy with the speed improvements.)
You can find documentation on getting access to the native collection elsewhere.
Following advice here:
https://stackoverflow.com/a/4588909/68567
customersNative.update(data.query, {$unset : {"slides.1" : 1 }}, function(err){
if(err) { return callback(err); }
customersNative.findAndModify(data.query, [],
{$pull: {'slides' : null } }, {safe: true, 'new' : true}, function(err, updated) {
//'updated' has new object
} );
});

Mongodb updates always first doc in array

I want to update one field in a document inside array. The problem is that it always updates the first element in array. I found out the problematic line (if I exclude it from the query updates work as expected).
var query = {
'_id': documentId,
'projectId': { $in: userProjectIds }, // this line is causing problems, if I exclude it from the query, update works as expected
'comments.id': id
};
var update = {
$set: {
'comments.$.content': content
}
};
Any ideas why is this happening?
EDIT: It is a bug in mongoose.js 3.6.x series, it is fixed in 3.8.x (tested on 3.8.3). Thank you all for your answers, which helped me to pinpoint the bug.

Resources