How to check whether a document was inserted or updated when using findOneAndUpdate?
Asked Answered
S

6

7
Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
    if(err) console.log(err);

    console.log("DOC " + doc)

    if(doc.status) {
        // FOUND ROOM SATTUS IS TRUE LOGIC 
        console.log(doc);
        // return callback(true)
    }
 });

Above query will return to me the actual document that's updated or inserted but I can't check exactly which one it is. If I do an update instead of findOneandUpdate I'm returned this

{
    ok: 1,
    nModified: 0,
    n: 1,
    upserted: [ { index: 0, _id: 55df883dd5c3f7cda6f84c78 } ]
} 

How do I return both the document and the write result or at least the upserted field from the write result.

Shippen answered 27/8, 2015 at 22:36 Comment(12)
If it is "upserted" then the only content that can possibly be in the document is the content you sent in the operation. In this case the "RoomName", "status" and "userNum" fields, and all with the content you specified here. The only thing other than what you send that can possibly be created is the _id value. So this is why that is the only thing returned. Understand? You don't need anything returned since your the one that already has all the values.Separates
Right but for the findOneAndUpdate if I say new:true , is there no way to distinguish whether that returning document was inserted or found? If however I don't include new:true then doc in this case would return null when the doc cant be found and then i assume it's inserted but im wondering if there's a better way about it lookShippen
You clearly do not understand. Read what I said again. You send the values for three fields, you get told a new document was created and the new _id of that document. No other fields are created other than the values you asked it to set. You already know the values, therefore there is no point in returning a document that just contains those values. That is how it works.Separates
I do not get the write result object for findOneAndUpdate that is only for update I was wondering how to get that for the findOneAndUpdate along with the document.Shippen
but i see it's not possibleShippen
The point is it's not necessary. And that is what you need to understand. Again if the document is "modified" then you "will" get the modified document returnedSeparates
Yea, it does seem redundant. So I can use either update or findOneAndUpdate for the same means. Is there any preference here?Shippen
Huh? Two completely different things. The purpose of .update() is to simply update, and possibly for multiple document. .findOneAndUpdate() also does exactly what it is named, and will either return the "modified" document or the "original" when a modification is made. If you want that return data for "modified" documents and one at a time, then that is what you use. BTW, the default new: false is set that way so it is easy to "tell" if the modification you sent actually changed anything, since the data you asked to change will have a different value in the document returned.Separates
If I use upsert: true with .update() or .findOneAndUpdate() they will both do what I'm trying to do which is pretty much a findOrCreate since I'm not actually updating any information.Shippen
Well "findOrCreate" pretty much implies fetching a document, so the correct term would be "updateOrCreate" which specifies the difference between the two. Both either "modify" on match or "upsert" where there is no match. So the difference here is in the word "find"Separates
It seems I will need the fetch component so I'll go with findOneAndUpdate(), thanks for clarifying.Shippen
So there is no way to get the _id field of your upserted document while still being able to check whether you inserted or not when using findOneAndUpdate() ?Shippen
S
4

Alright so my main problem was that I couldn't get the _id of the document I inserted without not being able to check whether if it was updated/found or inserted. However I learned that you can generate your own Id's.

id = mongoose.Types.ObjectId();    
Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {_id: id, status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
        if(err) console.log(err);

        if(doc === null) {

           // inserted document logic 
           // _id available for inserted document via id 
        } else if(doc.status) {

         // found document logic 
        }
     });

Update

Mongoose API v4.4.8

passRawResult: if true, passes the raw result from the MongoDB driver as the third callback parameter.

Shippen answered 30/8, 2015 at 6:18 Comment(1)
You don't need to do that.Separates
F
6

As of 8 August 2019 (Mongoose Version 5.6.9), the property to set is "rawResult" and not "passRawResult":

M.findOneAndUpdate({}, obj, {new: true, upsert: true, rawResult:true}, function(err, d) {
    if(err) console.log(err);
    console.log(d);
});

Output:

{ lastErrorObject:
   { n: 1,
     updatedExisting: false,
     upserted: 5d4befa6b44b48c3f2d21c75 },
  value: { _id: 5d4befa6b44b48c3f2d21c75, rating: 4, review: 'QQQ' },
  ok: 1 }

Notice also the result is returned as the second parameter and not the third parameter of the callback. The document can be retrieved by d.value.

Fontenot answered 8/8, 2019 at 9:55 Comment(0)
S
4

Alright so my main problem was that I couldn't get the _id of the document I inserted without not being able to check whether if it was updated/found or inserted. However I learned that you can generate your own Id's.

id = mongoose.Types.ObjectId();    
Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {_id: id, status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
        if(err) console.log(err);

        if(doc === null) {

           // inserted document logic 
           // _id available for inserted document via id 
        } else if(doc.status) {

         // found document logic 
        }
     });

Update

Mongoose API v4.4.8

passRawResult: if true, passes the raw result from the MongoDB driver as the third callback parameter.

Shippen answered 30/8, 2015 at 6:18 Comment(1)
You don't need to do that.Separates
I
4

Version 4.1.10 of Mongoose has an option called passRawResult which if set to true causes the raw parameter to be passed. Leaving out this option seems to default to false and cause raw to always be undefined:

passRawResult: if true, passes the raw result from the MongoDB driver as the third callback parameter

http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate

Immortal answered 6/10, 2015 at 8:10 Comment(0)
Y
0

I'm afraid Using FindOneAndUpdate can't do what you whant because it doesn't has middleware and setter and it mention it the docs:

Although values are cast to their appropriate types when using the findAndModify helpers, the following are not applied:

  • defaults
  • Setters
  • validators
  • middleware

http://mongoosejs.com/docs/api.html search it in the findOneAndUpdate
if you want to get the docs before update and the docs after update you can do it this way :

Model.findOne({ name: 'borne' }, function (err, doc) {
if (doc){ 
  console.log(doc);//this is ur document before update
  doc.name = 'jason borne';
  doc.save(callback); // you can use your own callback to get the udpated doc
}
})

hope it helps you

Yorick answered 28/8, 2015 at 23:48 Comment(6)
Thanks, but my current problem is that I can't access room.roomname in the scope of the callback for findOneAndUpdateShippen
as I mention in my answer you can't access the docs using findOneAndUpdate, because it doesn't have validators and this will not return the document you just updated. I suggest you to read the docs there are plenty of information about the API.Yorick
If I do true:new it will return it but then I can't tell if it was inserted or found.Shippen
oh wait, can you log the room.Roomname maybe the varibel is null so you didn't get updateYorick
Yea the variable doc is null, in that case I'd want to return room.Roomname but that's out of scope?Shippen
yes that would be out of scope. you can ask another question for that.Yorick
S
0

I don't know how this got completely off track, but there as always been a "third" argument response to all .XXupdate() methods, which is basically the raw response from the driver. This always tells you whether the document is "upserted" or not:

Chatrooms.findOneAndUpdate(
   { "Roomname": room.Roomname },
   { "$setOnInsert": { 
      "status": true, "userNum": 1
   }},
   { "new": true, "upsert": true },
   function(err, doc,raw) {
    if(err) console.log(err);

    // Check if upserted
    if ( raw.lasErrorObject.n == 1 && !raw.lastErrorObject.updatedExisting ) {
        console.log("upserted: %s", raw.lastErrorObject.upserted);
    }

    console.log("DOC " + doc)

    if (doc.status) {
        // FOUND ROOM SATTUS IS TRUE LOGIC 
        console.log(doc);
        // return callback(true)
    }
});

Which will tell you the _id of the document that was just upserted.

From something like this in the "raw" response:

{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e12c65f6044f57c8e09a46 },
  value: { _id: 55e12c65f6044f57c8e09a46, 
           status: true,
           userNum: 1
           __v: 0 },
  ok: 1 }

Complete reproducible listing:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var testSchema = new Schema({
  name: String
});

var Test = mongoose.model('Test', testSchema, 'test');

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.eachSeries(
        ["first","second"],
        function(it,callback) {
          console.log(it);
          Test.findOneAndUpdate(
            { "name": "Bill" },
            { "$set": { "name": "Bill" } },
            { "new": true, "upsert": true },
            function(err,doc,raw) {
              console.log(raw),
              console.log(doc),
              callback(err);
            }
          );
        },
        callback
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Which outputs:

first
{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e2a92328f7d03a06a2dd6b },
  value: { _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 },
  ok: 1 }
{ _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 }
second
{ lastErrorObject: { updatedExisting: true, n: 1 },
  value: { _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 },
  ok: 1 }
{ _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 }
Separates answered 30/8, 2015 at 6:18 Comment(13)
raw is undefined for me. any idea why?Shippen
@SarodhUggalla Not possible. It is only ever undefined when there is no match or upsert. With upsert:true there is always a response. That is because this is where mongoose gets it's response from in the first place. You are doing something wrong/different.Separates
Chatrooms.findOneAndUpdate({Roomname: RoomHash.roomhash},{ $setOnInsert: { userNum: 1, status: true}}, {new: true,upsert: true}, function(err, doc, raw) { console.log("DOC: %j" , doc); console.log("RAW: %j", raw); if(err) console.log(err);Shippen
exactly the same as far as i can see.Shippen
@SarodhUggalla What is your mongoose version?Separates
I hope there's a reason for this because this is exactly what I need.Shippen
@SarodhUggalla Attached a complete listing that is reproducible. Actually using latest 4.1.6 on install, but there should be no difference. You are doing something wrong. Use it to compare. I know it works and the listing output is exactly shown from running the provided code. I also know because I have been explaining the difference using "promises" here: https://mcmap.net/q/1478386/-mongoose-promises-how-do-you-check-if-document-was-created-using-findoneandupdate-with-upsertSeparates
first undefined { _id: 55e2aa26d5c3f7cda6f84cfd, name: 'Bill', __v: 0 } second undefined { _id: 55e2aa26d5c3f7cda6f84cfd, name: 'Bill', __v: 0 }Shippen
@SarodhUggalla Dude. Brand new directory and npm init etc etc. That is what "reproducible" means. Get out of your own project directory and whatever problems you have caused there. That is the point here.Separates
it worked this time though the version is [email protected]Shippen
@SarodhUggalla Of course it does. Things work as designed. Things in your own project ie dependencies, plugins, your own code, are breaking the normal functionality. That's your job to sort out what the problem is. Add dependency tests to your projects in the future. ie. Make sure that basic things like this are not being broken. The issue is "Solved" BTW. If your project breaks it, then that is not up to the answer provided here to solve. Nor was it your question.Separates
@SarodhUggalla As already stated. That is up to you to work out. Even asking another question without further investigation from yourself would be "too broad" a topic. I cannot see your whole codebase, so I don not know what you have done. But it is not mongoose versions. At least not within the 4.x series.Separates
@SarodhUggalla Well you got an answer then. Don't forget to accept it.Separates
B
0

In mongoose 8.1.3, the property to set is "includeResultMetadata" instead of "rawResult" or "passRawResult".

Bespangle answered 23/2 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.