Mongoose: Get doc _id after upsert - node.js

is there any way to get the record _id after an upsert?
I've seen this post (How to insert a doc into mongodb using mongoose and get the generated id?), but this is oriented only to inserts, not updates.
Also, using the MongoDB you can use get the _id using getlasterror (https://groups.google.com/forum/?fromgroups=#!topic/mongoose-orm/ehZ11QY-OUw), but Mongoose doesn't provides access to it (https://groups.google.com/forum/?fromgroups=#!topic/mongoose-orm/pSv6WrasvWg)
Thanks

Use Mongoose's findOneAndUpdate method with upsert: true in the options object.
var query = { name: 'borne' },
data = { name: 'jason borne' },
options = { upsert: true };
Model.findOneAndUpdate(query, data, options, function (err, object) {
/* use object._id */
});

Another possibility with promises:
Model.findOneAndUpdate(query, data, options, function (err, object) {
})).then((result) => {
/* return result.upserted[0]._id */
})
...where result is the following:
{ n: 1,
nModified: 0,
upserted: [ { index: 0, _id: /* some id */ } ],
ok: 1 }

If you want the updated document returned, and in cases where it didn't exist and was upserted the new document. Below is the option you need to set.
set new: true to the options: options = { upsert: true, new: true };
Source: Based on Jamiel's comment, I am adding his comment as an answer as it was hard time finding for me to get that _id when no document existed and created by upsert (And I was trying to create my own extended method).

Related

MongoDB: Upsert a document and return updated document

I m trying to run a query where I m trying to upsert a document using findOneAndUpdate in my backend Node JS api using mongodb-nodejs native driver. The problem I m facing is that, it is not returning the updated document and I m getting null, whenever the upsert is happening if no matching document is present.
I have already tried add new option but it doesn't seem to work with findOneAndUpdate. My query is as follows:
collection.findOneAndUpdate(
filter,
{ $inc: { att1: value1 } },
{ upsert: true, new: true }
);
Try
collection.findOneAndUpdate(
filter,
{ $inc: { att1: value1 } },
{ upsert: true, returnOriginal: false }
);
The reason why you get null is the default returnOriginal:true. If there is no match, returns null.
The documentation is rather poor respect to what it returns when no match happens. So I say "Try".

Create document using Mongoose.js with Projection and Unique validation

Is there any way to have the same options as findOneAndUpdate() when creating a document. I realize I can set upsert: true but I am looking to throw an error if the document already exist.
So say I have Courses in a database and when to insert a new one. If I use .create() I get no ability to project, or use lean. :dis
If I use findOneAndUpdate then it does not throw an Error for a document with a duplicate field as it is updating as existing document which I do not want to do.
Basically I would like the abilities that come with .findOneAndUpdate() but I want it to throw an error if it finds a document, since I am searching for it based off the field which I wish to be unique
By looking to your problem , you can try the following approach
function updateUser(user,cb){
UserModel.find({name : user.name}, function (err, docs) {
if (docs.length){
cb('Name exists already',null);
}else{
user.save(function (err, returned) {
var leanObject = returned.toObject();
assert.equal(leanObject.schema, null);
});
}
});
}
So , in the above code , you find for the document and and if we get the result non-empty , we can consider it as an error otherwise we can save that document as new entry .
So to accomplish the desired goals I have done the below. Where I use a query condition that I know will fail. That way I am forcing the upsert and get the benefits of the options.
async addCourse(input, projection = {}) {
const { name } = input;
const conditions = { _id: mongoose.Types.ObjectId() };
const newCourse = await this.model.findOneAndUpdate(conditions, input, {
upsert: true,
new: true,
lean: true,
projection,
runValidators: true,
context: 'query',
setDefaultsOnInsert: true,
});
return newCourse;
}

Mongoose get collection data if present the update otherwise insert

I am new to mongodb and mongoose with ExpressJS. It may be simple question but I dont success.
var BookCounter = new Schema({
counter: {type: Number,},
book: {type: String, min: 18}
});
this is the my schema and the following is the my question:
I want to check that xyz book is present or not
if book is present
Then I have to update the book counter by one.
otherwise
I have to insert new book.
Can you please help me ..
Thanks in advance :)
You need an update operation findOneAndUpdate() that uses the options upsert to create the document if it does not exist and the new option if set to true returns the newly created/modified document rather than the original, alongside the $set and $inc field update operators.
The following example demonstrates this:
var query = { "book": "xyz" },
update = { "$inc": { "counter": 1 } },
options = { "upsert": true, "new": true };
// Find the document
Book.findOneAndUpdate(query, update, options, function(err, result) {
if (err) handleError(err);
else {
// do something with the document
console.log(JSON.stringify(result, null, 4));
}
});

Mongoose: how to check if document is modified via model.findOneAndUpdate()

In mongoose, we can check if an update operation has modified the document with model.update():
model.update(query, update, function(err, raw){
if (raw.nModified >= 1) console.log('document is modified!')
});
Is there a way to do the same with model.findOneAndUpdate()?
model.findOneAndUpdate(query, update, { new: true }, function(err, doc){
if (doc) {
// So MongoDB found the document, but is there a way
// to know the document was indeed modified?
}
});
You can pass the option { passRawResult : true } to mongoose to advice mongoose to pass the raw result of the underlying mongodb driver, in this case mongodb-native, as a third argument to the callback.
mongodb-native documentation for findOneAndUpdate
model.findOneAndUpdate(query, update, { new: true, passRawResult : true }, function(err, doc, res){
// res will look like
// { value: { _id: 56a9fc80a7f9a4d41c344852, name: 'hugo updated', __v: 0 },
// lastErrorObject: { updatedExisting: true, n: 1 },
// ok: 1 }
});
In case the update did not succeed due to no matching document was found a null res will be passed to the callback. In case a document matched but field values where the same as before the update res object will not give you enough information to figure out if values were updated for the matching document.

How to properly do a Bulk upsert/update in MongoDB

I'm trying to:
Find a document according to a search criteria,
If found, update some attributes
If not insert a document with some attributes.
I'm using a Bulk.unOrderedOperation as I'm also performing a single insert. And I want to do everything in one operation againast DB.
However something it's causing nothing is being inserted for the update/upsert operation.
This is the insert document:
var lineUpPointsRoundRecord = {
lineupId: lineup.id, // String
totalPoints: roundPoints, // Number
teamId: lineup.team, // String
teamName: home.team.name, // String
userId: home.iduser, // String
userName: home.user.name, // String
round: lineup.matchDate.round, // Number
date: new Date()
}
This is the upsert document:
var lineUpPointsGeneralRecord = {
teamId: lineup.team, // String
teamName: home.team.name, // String
userId: home.iduser, // String
userName: home.user.name, // String
round: 0,
signupPoints: home.signupPoints, // String
lfPoints: roundPoints+home.signupPoints, // Number
roundPoints: [roundPoints] // Number
};
This is how I'm trying to upsert/update:
var batch = collection.initializeUnorderedBulkOp();
batch.insert(lineUpPointsRoundRecord);
batch.find({team: lineUpPointsRoundRecord.teamId, round: 0}).
upsert().
update({
$setOnInsert: lineUpPointsGeneralRecord,
$inc: {lfPoints: roundPoints},
$push: {roundPoints: roundPoints}
});
batch.execute(function (err, result) {
return cb(err,result);
});
Why wouldn't it be upserting/updating?
Note
That is JS code using waterline ORM which also uses mongodb native driver.
Your syntax here is basically correct, but your general execution was wrong and you should have "seperated" the "upsert" action from the other modifications. These will otherwise "clash" and produce an error when an "upsert" occurs:
LineupPointsRecord.native(function (err,collection) {
var bulk = collection.initializeOrderedBulkOp();
// Match and update only. Do not attempt upsert
bulk.find({
"teamId": lineUpPointsGeneralRecord.teamId,
"round": 0
}).updateOne({
"$inc": { "lfPoints": roundPoints },
"$push": { "roundPoints": roundPoints }
});
// Attempt upsert with $setOnInsert only
bulk.find({
"teamId": lineUpPointsGeneralRecord.teamId,
"round": 0
}).upsert().updateOne({
"$setOnInsert": lineUpPointsGeneralRecord
});
bulk.execute(function (err,updateResult) {
sails.log.debug(err,updateResult);
});
});
Make sure your sails-mongo is a latest version supporting the Bulk operations properly be the inclusion of a recent node native driver. The most recent supports the v2 driver, which is fine for this.
I recommend use bulkWrite exemplary code with bulk upsert of many documents:
In this case you will create documents with unique md5. If document exists then will be updated but no new document is created like in classical insertMany.
const collection = context.services.get("mongodb-atlas").db("master").collection("fb_posts");
return collection.bulkWrite(
posts.map(p => {
return { updateOne:
{
filter: { md5: p.md5 },
update: {$set: p},
upsert : true
}
}
}
),
{ ordered : false }
);
https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/
Typically I have always set upsert as a property on update. Also update should be able to find the record itself so no need to find it individually.
Depending on the environment the $ may or may not be necessary.
batch.update(
{team: lineUpPointsRoundRecord.teamId, round: 0},
{
$setOnInsert: lineUpPointsGeneralRecord,
$inc: {lfPoints: roundPoints},
$push: {roundPoints: roundPoints},
$upsert: true
});

Resources