Understanding mongoose findOne().remove() - node.js

As you know, in mongoose, we can remove all users with age 30 like this:
User.find({age: 30}).remove(callback);
Now, replace find() with findOne(), and I think it should remove only 1 user:
User.findOne({age: 30}).remove(callback);
oh, not as I expected, the code above also remove ALL instead of ONE
So, why findOne().remove() remove ALL instead of ONE? Is that a bug or a feature and why?
Thanks in advance!
P/S: I know findOneAndRemove() would remove one user for me, but in this question I want to understand findOne().remove()

I have reported this question to mongoose team, and got a reply:
https://github.com/LearnBoost/mongoose/issues/1851#issuecomment-31355346
Here's the message from aheckmann
"that's a good catch. findOne just sets the command name to run, remove() changes it back to a rice command but no limit was ever set. We should probably change that in 3.9 so that findOne sets
the limit as well."

Both find and findOne returns mongoose Query objects which only contains information about the model and the specified query. It's not taking into account findOne which is applied first in the callback. What you expect to happen is to have options be set like this User.findOne({age: 30}, null, {limit: 1}).remove() as this would only remove one and you could argue that this is a bug, but that depends on the usage. Like you have already pointed out, the right way to go is to use findOneAndRemove().

I'm kind of a noob but wouldn't you need to put your remove in the callback because this is an asynchronous function? Try something like:
User.findOne({age: 30}, function(err, user){
user.remove()
})

Related

Consecutive calls to updateOne of mongodb: 3rd one does not work

I receive 3 post calls from client, let say in a second, and with nodejs-mongodb immediately(without any pause, sleep, etc) I try to insert the data that is posted in database using updateOne. All data is new, so in every call, insert would happen.
Here is the code (js):
const myCollection = mydb.collection("mydata")
myCollection.updateOne({name:req.data.name},{$set:{name:req.data.name, data:req.data.data}}, {upsert:true}, function(err, result) {console.log("UPDATEONE err: "+err)})
When I call just 1 time this updateOne, it works; 2 times successively, it works. But if I call 2+ times in succession, only the first two ones correctly inserted into database, and the rest, no.
The error that I get after updateOne is, MongoWriteConcernError: No write concern mode named 'majority;' found in replica set configuration. However, I always get this error, also even when the insertion is done correctly. So I don't think this is related to my problem.
Probably you will suggest to me to use updateMany, bulkWrite, etc. and you will be right, but I want to know the reason why after 2+ the insertion is not done.
Have in mind .updateOne() returns a Promise so it should be handled properly in order to avoid concurrency issues. More info about it here.
The error MongoWriteConcernError might be related to the connection string you are using. Check if there is any &w=majority and remove it as recommended here.

Mongoose update middleware - need to create hooks for every single update middleware?

Let's say I have the following schema:
PersonSchema = {
name: String,
timesUpdated: {
type: Number,
default: 0
}
}
Every time that the given person is updated, I would want the timesUpdated field to increment by one. Now, I could use Mongoose's update middleware hook, which would be called by something like
PersonModel.update({_id: <id>}, {name: 'new name'})
and my timesUpdated field would be appropriately incremented. However, if I only wrote a hook for the update middleware, the following code would not update my timesUpdated field:
PersonModel.updateOne({_id: <id>}, {name: 'new name'})
In order for my count to be updated, I would have to write middleware for the udpateOne query. This pattern repeats for several other similar middleware hooks, such as updateMany, replaceOne, save (if you want to update a document this way), findOneAndUpdate and I'm sure many others.
I use the example of an updated count for simplicity, but I could also have used an example where some other unrelated action happens upon changing my name. Am I missing something in how hooks should be used, or is this a limitation of mongoose hooks?
Pre save hook will only be executed with following functions according to mongoose's middleware document.
init
validate
save
remove
However update functions are working directly with MongoDB, therefor there is no general use hook applies on all update functions. See related discussion on Github.
I'd suggest using a function to perform your task before/after all required calls (to update or updateOne) rather a hook, because of the limitations mentioned in the other answer and the question.
Or perhaps limit the kinds of methods that can be called to the ones that have the hook set.
Or use a hook which will always get called in the middle-ware sequence, like a validate hook.

Specify returned fields in Node.js / Waterline?

I want to make a request like:
User.find().exec(function(){});
I know I can use toJSON in the model however I don't like this approach since sometimes I need different parameters. For instance if it's the logged in user I will return their email and other parameters. However if it the request fort he same data is made by a different user it would not include the email and a smaller subset of parameters.
I've also tried using:
User.find({}, {username:1}) ...
User.find({}, {fields: {username:1}});
But not having any luck. How can I specify the fields I need returned?
So actually found a weird workaround for this. the fields param WILL work as long as you pass other params with it such as limit or order:
User.find({}, {fields: {username:1}}).limit(1);
Note that this will NOT work with findOne or any of the singular returning types. This means in your result callback you will need to do user[1].
Of course the other option is to just scrub your data on the way out, which is a pain if you are using a large list of items. So if anything this works for large lists where you might actually set limit(20) and for single items you can just explicitly return paras until select() is available.
This is an update to the question, fields is no longer used in sails 11, please use select instead of fields.
Model.find({field: 'value'}, {select: ['id', 'name']})
.paginate({page: 1}, {limit: 10})
.exec(function(err, results) {
if(err) {
res.badRequest('reason');
}
res.json(results);
});
Waterline does not currently support any "select" syntax; it always returns all fields for a model. It's currently in development and may make it into the next release, but for now the best way to do what you want would be to use model class methods to make custom finders. For example, User.findUser(criteria, cb) could find a user give criteria, and then check whether it was the logged-in user before deciding which data to return in the callback.

Model.create does not work on very large array of documents

I am facing a problem with mongoose Model.create method.
When i call
Model.create(arrayOfThousandDocs, function(err){});
After 15 min (sufficient for all the docs to get saved) when i switch to mongo shell and query upon total no of docs saved
then i find only something around 700-800 (no of docs saved varies every time Model.create is called).
And mongoose or mongo returns no any error.
Have anyone faced the same bug?
Please tell me how to resolve it.
it may be because, Model.create uses forEach method, and that is not suitable in nodejs asynchronous mode programming.. Please correct me if if i am wrong..
and suggest your views..
here is the source : http://mongoosejs.com/docs/api.html#model_Model.create

does mongoose have an isDirty check?

I have a mongoose setup which involves an embedded-schema, lets say: A Blogpost with embedded comments. Comments can be edited by the original publisher as well as by an editor/admin. After adding / editing a comment the entire blogpost is saved.
I have some custom mongoose's 'pre' middleware set up on the embedded comment-schema which automatically sets the lasteditdate for that particular comment.
The thing is that 'pre' is called on EVERY comment on the blogpost, since I call save() on the blogpost. (For other reasons I need to do it like this) . Therefore, I need a way to check which comments have changed (or are new) since they were last saved (as part of the Blogpost overall save())
The questio: how to check in 'pre' whether a comment has changed or not? Obviously calling this.isNew isn't sufficient, since comments could be edited (i.e: aren't new) as well.
Is there any isDirty or similar that I'm overlooking?
For version 3.x
if(doc.isModified()){
// do stuff
}
In Mongoose you can use the Document method isModified(#STRING).
The most recent documentation for the method can be found here.
So to check a specific property with doc.isModified you can do this:
doc.comments[4].message = "Hi, I've made an edit to my post";
// inside pre hook
if ( this.isModified('comments') ) {
// do something
}
If you want to check a specific comment you can do that with the following notation this.isModified('comments.0.message')
Since the argument takes a string if you needed to know specifically which comment was modified you could loop through each comment and run this.isModified('comments['+i+']message')
You may use the modified getter:
if (doc.modified) {
// :)
}
This may be relevant to Mongoose users circa mid-2020 who are seeing this error:
"errorType": "TypeError",
"errorMessage": "Cannot set property 'isDirty' of null",
Upgrade to the latest version of Mongoose to fix it.
https://github.com/Automattic/mongoose/issues/8719

Resources