Mongoose overwrite the document rather that `$set` fields - node.js

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?

Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.

You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

Related

Update document inside array of objects in mongoose

I need to update objects inside an array so I'm trying but I get the following error:
error Plan executor error during findAndModify :: caused by :: The
positional operator did not find the match needed from the query.
This is my code:
const payment = await Purchase.findByIdAndUpdate(
{ '_id': req.body.id, 'payments._id': req.body.paymentId },
{
$set: {
'payments.$.status': false
}
}
,{ new: true });
payments object on Model:
payments: [
{
createdBy: [Object],
createdAt: '08/13/22',
paymentNumber: 0,
previousBalance: 3747.68,
paymentAmount: 3747.68,
outstandingBalance: 0,
status: true,
_id: new ObjectId("62f83f3c22e4f67dde8cb85a"),
lastModificationBy: [],
disabledBy: []
}
]
while using fineByIdAndUpdate you only need to pass id of document to be updated.
const payment = await Purchase.findByIdAndUpdate(req.body.paymentId,{status:false},{new:true} )
for using findByIdAndUpdate, you need to add this runValidators
const payment = await Purchase.findByIdAndUpdate(req.params.id,
'payments.$.status': false
}, {runValidators: true}
while same update can be done by this as well
const payment = await Purchase.findByIdAndUpdate(
req.body.paymentId,
{
status:false
}, {
new:true
}
)

Cannot read property 'ClientSession' of undefined [duplicate]

I am using MongoDB Atlas cloud(https://cloud.mongodb.com/) and Mongoose library.
I tried to create multiple documents using transaction concept, but it is not working.
I am not getting any error. but, it seems rollback is not working properly.
app.js
//*** more code here
var app = express();
require('./models/db');
//*** more code here
models/db.js
var mongoose = require( 'mongoose' );
// Build the connection string
var dbURI = 'mongodb+srv://mydb:pass#cluster0-****.mongodb.net/mydb?retryWrites=true';
// Create the database connection
mongoose.connect(dbURI, {
useCreateIndex: true,
useNewUrlParser: true,
});
// Get Mongoose to use the global promise library
mongoose.Promise = global.Promise;
models/user.js
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
userName: {
type: String,
required: true
},
pass: {
type: String,
select: false
}
});
module.exports = mongoose.model("User", UserSchema, "user");
myroute.js
const db = require("mongoose");
const User = require("./models/user");
router.post("/addusers", async (req, res, next) => {
const SESSION = await db.startSession();
await SESSION.startTransaction();
try {
const newUser = new User({
//*** data for user ***
});
await newUser.save();
//*** for test purpose, trigger some error ***
throw new Error("some error");
await SESSION.commitTransaction();
//*** return data
} catch (error) {
await SESSION.abortTransaction();
} finally {
SESSION.endSession();
}
});
Above code works without error, but it still creates user in the DB. It suppose to rollback the created user and the collection should be empty.
I don't know what I have missed here. Can anyone please let me know whats wrong here?
app, models, schema and router are in different files.
You need to include the session within the options for all read/write operations which are active during a transaction. Only then are they actually applied to the transaction scope where you are able to roll them back.
As a bit more complete listing, and just using the more classic Order/OrderItems modelling which should be pretty familiar to most people with some relational transactions experience:
const { Schema } = mongoose = require('mongoose');
// URI including the name of the replicaSet connecting to
const uri = 'mongodb://localhost:27017/trandemo?replicaSet=fresh';
const opts = { useNewUrlParser: true };
// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
// schema defs
const orderSchema = new Schema({
name: String
});
const orderItemsSchema = new Schema({
order: { type: Schema.Types.ObjectId, ref: 'Order' },
itemName: String,
price: Number
});
const Order = mongoose.model('Order', orderSchema);
const OrderItems = mongoose.model('OrderItems', orderItemsSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
// main
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
)
let session = await conn.startSession();
session.startTransaction();
// Collections must exist in transactions
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.createCollection())
);
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
let items = await OrderItems.insertMany(
[
{ order: order._id, itemName: 'Cheese', price: 1 },
{ order: order._id, itemName: 'Bread', price: 2 },
{ order: order._id, itemName: 'Milk', price: 3 }
],
{ session }
);
// update an item
let result1 = await OrderItems.updateOne(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ session }
);
log(result1);
// commit
await session.commitTransaction();
// start another
session.startTransaction();
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
/*
* $lookup join - expect Milk to be price: 4
*
*/
let joined = await Order.aggregate([
{ '$match': { _id: order._id } },
{ '$lookup': {
'from': OrderItems.collection.name,
'foreignField': 'order',
'localField': '_id',
'as': 'orderitems'
}}
]);
log(joined);
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
So I would generally recommend calling the variable session in lowercase, since this is the name of the key for the "options" object where it is required on all operations. Keeping this in the lowercase convention allows for using things like the ES6 Object assignment as well:
const conn = await mongoose.connect(uri, opts);
...
let session = await conn.startSession();
session.startTransaction();
Also the mongoose documentation on transactions is a little misleading, or at least it could be more descriptive. What it refers to as db in the examples is actually the Mongoose Connection instance, and not the underlying Db or even the mongoose global import as some may misinterpret this. Note in the listing and above excerpt this is obtained from mongoose.connect() and should be kept within your code as something you can access from a shared import.
Alternately you can even grab this in modular code via the mongoose.connection property, at any time after a connection has been established. This is usually safe inside things such as server route handlers and the like since there will be a database connection by the time that code is called.
The code also demonstrates the session usage in the different model methods:
let [order, other] = await Order.insertMany([
{ name: 'Bill' },
{ name: 'Ted' }
], { session });
let fred = new Order({ name: 'Fred' });
await fred.save({ session });
All the find() based methods and the update() or insert() and delete() based methods all have a final "options block" where this session key and value are expected. The save() method's only argument is this options block. This is what tells MongoDB to apply these actions to the current transaction on that referenced session.
In much the same way, before a transaction is committed any requests for a find() or similar which do not specify that session option do not see the state of the data whilst that transaction is in progress. The modified data state is only available to other operations once the transaction completes. Note this has effects on writes as covered in the documentation.
When an "abort" is issued:
// Update and abort
let result2 = await OrderItems.findOneAndUpdate(
{ order: order._id, itemName: 'Milk' },
{ $inc: { price: 1 } },
{ 'new': true, session }
);
log(result2);
await session.abortTransaction();
Any operations on the active transaction are removed from state and are not applied. As such they are not visible to resulting operations afterwards. In the example here the value in the document is incremented and will show a retrieved value of 5 on the current session. However after session.abortTransaction() the previous state of the document is reverted. Note that any global context which was not reading data on the same session, does not see that state change unless committed.
That should give the general overview. There is more complexity that can be added to handle varying levels of write failure and retries, but that is already extensively covered in documentation and many samples, or can be answered to a more specific question.
Output
For reference, the output of the included listing is shown here:
Mongoose: orders.deleteMany({}, {})
Mongoose: orderitems.deleteMany({}, {})
Mongoose: orders.insertMany([ { _id: 5bf775986c7c1a61d12137dd, name: 'Bill', __v: 0 }, { _id: 5bf775986c7c1a61d12137de, name: 'Ted', __v: 0 } ], { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orders.insertOne({ _id: ObjectId("5bf775986c7c1a61d12137df"), name: 'Fred', __v: 0 }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orderitems.insertMany([ { _id: 5bf775986c7c1a61d12137e0, order: 5bf775986c7c1a61d12137dd, itemName: 'Cheese', price: 1, __v: 0 }, { _id: 5bf775986c7c1a61d12137e1, order: 5bf775986c7c1a61d12137dd, itemName: 'Bread', price: 2, __v: 0 }, { _id: 5bf775986c7c1a61d12137e2, order: 5bf775986c7c1a61d12137dd, itemName: 'Milk', price: 3, __v: 0 } ], { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
Mongoose: orderitems.updateOne({ order: ObjectId("5bf775986c7c1a61d12137dd"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2") })
{
"n": 1,
"nModified": 1,
"opTime": {
"ts": "6626894672394452998",
"t": 139
},
"electionId": "7fffffff000000000000008b",
"ok": 1,
"operationTime": "6626894672394452998",
"$clusterTime": {
"clusterTime": "6626894672394452998",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: orderitems.findOneAndUpdate({ order: ObjectId("5bf775986c7c1a61d12137dd"), itemName: 'Milk' }, { '$inc': { price: 1 } }, { session: ClientSession("80f827fe077044c8b6c0547b34605cb2"), upsert: false, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf775986c7c1a61d12137e2",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Milk",
"price": 5,
"__v": 0
}
Mongoose: orders.aggregate([ { '$match': { _id: 5bf775986c7c1a61d12137dd } }, { '$lookup': { from: 'orderitems', foreignField: 'order', localField: '_id', as: 'orderitems' } } ], {})
[
{
"_id": "5bf775986c7c1a61d12137dd",
"name": "Bill",
"__v": 0,
"orderitems": [
{
"_id": "5bf775986c7c1a61d12137e0",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Cheese",
"price": 1,
"__v": 0
},
{
"_id": "5bf775986c7c1a61d12137e1",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Bread",
"price": 2,
"__v": 0
},
{
"_id": "5bf775986c7c1a61d12137e2",
"order": "5bf775986c7c1a61d12137dd",
"itemName": "Milk",
"price": 4,
"__v": 0
}
]
}
]
I think this is the quickest way to start performing transaction with mongoose
const mongoose = require("mongoose");
// starting session on mongoose default connection
const session = await mongoose.startSession();
mongoose.connection.transaction(async function executor(session) {
try {
// creating 3 collections in isolation with atomicity
const price = new Price(priceSchema);
const variant = new Variant(variantSchema);
const item = new Item(itemSchema);
await price.save({ session });
await variant.save({ session });
// throw new Error("opps some error in transaction");
return await item.save({ session });
} catch (err) {
console.log(err);
}
});

mongodb findOneAndUpdate replace document instead of updating specified field [duplicate]

Say, i have a document:
{
_id: 'some_mongodb_id',
name: 'john doe',
phone: '+12345678901',
}
I want to update this document:
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
And the result should be this:
{
_id: 'some_mongodb_id',
name: 'Dan smith',
}
The property, that is not specified, should be removed.
How do i do that?
Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.
So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:
As a full example:
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const testSchema = new Schema({
name: String,
phone: String
});
const Test = mongoose.model('Test', testSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.keys(conn.models).map( m => conn.models[m].remove({}) )
);
// Create a document
let test = await Test.create({
name: 'john doe',
phone: '+12345678901'
});
log(test);
// This update will apply using $set for the name
let notover = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Bill S. Preston' },
{ new: true }
);
log(notover);
// This update will just use the supplied object, and overwrite
let updated = await Test.findOneAndUpdate(
{ _id: test._id },
{ name: 'Dan Smith' },
{ new: true, overwrite: true }
);
log(updated);
} catch (e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Produces:
Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
"__v": 0,
"name": "john doe",
"phone": "+12345678901",
"_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Bill S. Preston",
"phone": "+12345678901",
"__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
"_id": "596efb0ec941ff0ec319ac1e",
"name": "Dan Smith"
}
Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.
Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.
NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.
This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.
You can pass upsert option, and it will replace document:
var collection = db.collection('test');
collection.findOneAndUpdate(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
console.log(doc);
}
);
But the problem here - is that doc in callback is found document but not updated.
Hence you need perform something like this:
var collection = db.collection('test');
collection.update(
{'_id': 'some_mongodb_id'},
{name: 'Dan smith Only'},
{upsert: true},
function (err, doc) {
collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
console.log(doc);
});
}
);

Mongoose $push keeps adding two entries

Here are my user and product schemas:
const productSchema = new Schema({
//...
addedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "users"
}
});
const userSchema = new Schema({
//...
addedItems: [{
type: mongoose.Schema.ObjectId,
ref: "products"
}]
});
mongoose.model("products", productSchema);
mongoose.model("users", userSchema);
In my Node back end route I do this query:
User.findOneAndUpdate(
{ _id: req.body.id },
{ $push: { addedItems: newProduct._id } },
{ upsert: true, new: true },
function(err, doc) {
console.log(err, doc);
}
);
The console.log prints out this:
{
//...
addedItems: [ 5ab0223118599214f4dd7803 ]
}
Everything looks good. I go to actually look at the data using the front-end website for my mongo db; I'm using mlab.com, and this is what shows:
{
//...
"addedItems": [
{
"$oid": "5ab0223118599214f4dd7803"
},
{
"$oid": "5ab0223118599214f4dd7803"
}
]
}
Question: What the heck happened? Why does it add an additional entry into addedItems ?! Even though my console.log only showed one.
Note:
I tested to see if the backend route was being called more than once. It is not.
It seems to be a problem with $push because if I just have { addedItems: newProduct._id } then only one entry goes in, but it overwrites the entire array.
Edit:
Made a test project to produce the same results: https://github.com/philliprognerud/test-mcve-stackoverflow
Can anyone figure out what's going on?
The problem is caused by your mixed used of promises (via async/await) and callbacks with the findOneAndUpdate call which ends up executing the command twice.
To fix the problem:
const updatedUser = await User.findOneAndUpdate(
{ id: userID },
{ $push: { addedItems: newProduct.id } },
{ upsert: true, new: true }
);
console.log(updatedUser);
Future readers note that the use of await isn't shown here in the question, but is in the MCVE.
I am facing similar issue. Just landed to this page. I find that previous answer is not very descriptive. So posting this:
export const updateUserHandler = async (req, res) => {
const request = req.body;
await User.findOneAndUpdate( //<== remove await
{ _id: request.id },
{ $push: { addedItems: newProduct._id } },
{ upsert: true, new: true },
(findErr, findRes) => {
if (findErr) {
res.status(500).send({
message: 'Failed: to update user',
IsSuccess: false,
result: findErr
});
} else {
res.status(200).send({
message: 'Success: to update user',
IsSuccess: true,
result: findRes
});
}
}
);
}
Here there are two async calls one is the async and other is await. Because of this there are two entries in the document. Just remove await from await User.findOneAndUpdate. It will work perfectly.
Thanks!!
When you await Query you are using the promise-like, specifically, .then() and .catch(() of Query. Passing a callback as well will result in the behavior you're describing.
If you await Query and .then() of Query simultaneously, would make the query execute twice
use:
await Model.findOneAndUpdate(query, doc, options)
OR
Model.findOneAndUpdate(query, doc, options, callback)
This code $push keeps adding two entries:
const ali={ "_id": "5eaa39a18e7719140e3f4430" };
// return await customerModel.findOneAndUpdate(
// ali,
// {
// "$push": {
// "address": [objAdr],
// },
// },
// function (error: any, success: any) {
// if (error) {
// console.log(error);
// } else {
// console.log(success);
// }
// }
// );
My solutions working true:
return await customerModel
.findOneAndUpdate(
{ _id: ids },
{ $push: { "address": objAdr } }
)
.catch((err: string | undefined) => new Error(err));

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

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.
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.
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
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.
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
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 }

Resources