Consider this code, where I need to either create or update a particular document.
Inbox.model.findOneAndUpdate({ number: req.phone.number }, {
number: req.phone.number,
country: req.phone.country,
token: hat(),
appInstalled: true
}, { new: true, upsert: true }).then(function(inbox){
/*
do something here with inbox, but only if the inbox was created (not updated)
*/
});
Does mongoose have a facility to be able to make the distinction between whether or not the documented was created or updated? I require new: true
because I need to call functions on the inbox
.
In the case of .findOneAndUpdate()
or any of the .findAndModify()
core driver variants for mongoose, the actual callback signature has "three" arguments:
function(err,result,raw)
With the first being any error response, then the modified or original document depending on options and the third which is a write result of the issued statement.
That third argument should return data much like this:
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e12c65f6044f57c8e09a46 },
value: { _id: 55e12c65f6044f57c8e09a46,
number: 55555555,
country: 'US',
token: "XXX",
appInstalled: true,
__v: 0 },
ok: 1 }
With the consistent field in there as lastErrorObject.updatedExisting
being either true/false
depending on the result of whether an upsert occured. Note that there is also a "upserted" value containing the _id
response for the new document when this property is false
, but not when it is true
.
As such you would then modify your handling to consider the third condition, but this only works with a callback and not a promise:
Inbox.model.findOneAndUpdate(
{ "number": req.phone.number },
{
"$set": {
"country": req.phone.country,
"token": hat(),
"appInstalled": true
}
},
{ "new": true, "upsert": true },
function(err,doc,raw) {
if ( !raw.lastErrorObject.updatedExitsing ) {
// do things with the new document created
}
}
);
Where I would also strongly suggest you use update operators rather than raw objects here, as a raw object will always overwrite the entire document, yet operators like $set
just affect the listed fields.
Also noting that any matching "query arguments" to the statement are automatically assigned in the new document as long as their value is an exact match that was not found.
Given that using a promise does not seem to return the additional information for some reason, then do not see how this is possible with a promise other than setting { new: false}
and basically when no document is returned then it's a new one.
You have all the document data expected to be inserted anyway, so it is not like you really need that data returned anyway. It is in fact how the native driver methods handle this at the core, and only respond with the "upserted" _id
value when an upsert occurs.
This really comes down to another issue discussed on this site, under:
Can promises have multiple arguments to onFulfilled?
Where this really comes down to the resolution of multiple objects in a promise response, which is something not directly supported in the native speicification but there are approaches listed there.
So if you implement Bluebird promises and use the .spread()
method there, then everything is fine:
var async = require('async'),
Promise = require('bluebird'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var testSchema = new Schema({
name: String
});
var Test = mongoose.model('Test',testSchema,'test');
Promise.promisifyAll(Test);
Promise.promisifyAll(Test.prototype);
async.series(
[
function(callback) {
Test.remove({},callback);
},
function(callback) {
var promise = Test.findOneAndUpdateAsync(
{ "name": "Bill" },
{ "$set": { "name": "Bill" } },
{ "new": true, "upsert": true }
);
promise.spread(function(doc,raw) {
console.log(doc);
console.log(raw);
if ( !raw.lastErrorObject.updatedExisting ) {
console.log( "new document" );
}
callback();
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
Which of course returns both objects and you can access then consistently:
{ _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 }
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e14b7af6044f57c8e09a4e },
value: { _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 },
ok: 1 }
Here is a full listing demonstrating the normal behaviour:
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) {
Test.findOneAndUpdate(
{ "name": "Bill" },
{ "$set": { "name": "Bill" } },
{ "new": true, "upsert": true }
).then(function(doc,raw) {
console.log(doc);
console.log(raw);
if ( !raw.lastErrorObject.updatedExisting ) {
console.log( "new document" );
}
callback();
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
For the record, the native driver itself does not have this issue as the response object is in fact it's only object returned aside from any error:
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var collection = db.collection('test');
collection.findOneAndUpdate(
{ "name": "Bill" },
{ "$set": { "name": "Bill" } },
{ "upsert": true, "returnOriginal": false }
).then(function(response) {
console.log(response);
});
});
So it is always something like this:
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e13bcbf6044f57c8e09a4b },
value: { _id: 55e13bcbf6044f57c8e09a4b, name: 'Bill' },
ok: 1 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With