Update Document including empty fields via Mongoose - node.js

I'm sending the following object for mongoose to update:
{
"name" : "John",
"fruit" : "5a3d678d9b1549979d81c6ba" // this is an objectID
}
and the method:
db_user.findByIdAndUpdate(req.params.id, req.body).then(function(user, err) {
res.send(user);
});
This works fine if I replace the fruit with another fruitID which is what I'm trying to update.
However, if I send an object with no fruit, it won't remove the fruit field from the user document. Such as:
{
"name" : "John"
}
It will update whatever name I pass, but it won't remove the fruit field. Why?
Also: I can't be checking for each field that I'm sending or not and do an $unset for each accordingly, because I'm sending a lot of fields. I just want Mongoose to override the old document with the new one.

I know this is a little complicated / ugly and I'm sure there is an easier way but this should work if you want to update on specific thing and not have it fill the other values in blank.
function updateItem(req, res) {
function GetObjectFromKeyValuePairs(pairs) {
var tmp = {};
for(var key in pairs)
if(key[0] !== "_")
if(pairs[key].length !== 0)
tmp[`${key}`] = `${pairs[key]}`;
return tmp;
}
let updateOnlyChangedVals = GetObjectFromKeyValuePairs(req.body);
DB.Item.update({_id: req.params.id}, {$set: updateOnlyChangedVals},{new:true}, (err, uItem) => {
if (err) { return console.log("index error: " + err); }
res.json(uItem);
});
}

Related

Updating nested array mongodb

I think there are multiple ways to do this, and that has me a little confused as to why I can't get it to work.
I have a schema and I would like to update Notes within it but I can't seem to do it. Additionally, if I want to return the notes how would I go about doing it?
schema :
{
_id : 1234
email : me#me.com
pass : password
stock : [
{
Ticker : TSLA
Market : Nasdaq
Notes : [
"Buy at 700",
"Sell at 1000"
]
},
{
Ticker : AAPL
Market : Nasdaq
Notes : [
"Buy at 110",
"Sell at 140"
]
},
]
}
Each user has a list of stocks, and each stock has a list of notes.
Here is what I have tried in order to add a note to the list.
router.post(`/notes/add/:email/:pass/:stock/:note`, (req, res) => {
var email = req.params.email
var pass = req.params.pass
var note = req.params.note
var tempStock = req.params.stock
userModel.findOne({email: email} , (err, documents)=>{
if (err){
res.send(err);
}
else if (documents === null){
res.send('user not found');
}else if (bcrypt.compareSync(pass , documents.pass)){
userModel.findOneAndUpdate({email : email , "stock.Ticker" : tempStock}, {$push : {Notes : note}} ,(documents , err)=>{
if(err){
res.send(err);
}else {
res.send(documents.stock);
}
})
}
})
})
Thanks :)
Currently, you are pushing the new note into a newly created Notes property inside the model instead of into the Notes of the concrete stock. I am not completely aware of the mongoose semantics but you need something like this:
userModel.findOneAndUpdate({ email: email, "stock.Ticker": tempStock }, { $push: { "stock.$.Notes": note } }, (documents, err) => {
$ gives you a reference to the currently matched element from the stock array.
For the second part, I am not sure what you mean by
Additionally, if I want to return the notes how would I go about doing it?
They should be returned by default if you're not doing any projection excluding them.
Also, as per the docs(and general practise), the callback for the findOneAndUpdate has a signature of
(error, doc) => { }
instead of
(documents, err) => { }
so you should handle that.

How can i remove a specified field from a mongoose document?

I would like to remove dynamically a field from a mongoose document.
What I have tried:
I am using
$unset
and when I type the value
"cars.BMW"
it works. But when I want to pass a parameter, which will be a car brand and then to unset it. I tried
{$unset: {"cars."+brand: " "}}
but the plus sign is unexpected.
You can use destructuring and Template literals:
{$unset: {[`cars.${brand}`]: " "}}
Dot notation doesn't work like that. In js, you would use bracket notation to create a property dynamically from a string.
exports.deleteBrand = function(req, res){
var brand = req.params.brand;
var query = {};
query["cars." + brand] = "";
Text.update(
{key: req.params.key},
{$unset: query},
function(err){ if(err) res.send(err);
res.json({message: "Car deleted!"});
});
}
The query variable here translates to: {"cars.bmw": ""} if the brand is "bmw".
EDIT: Edited to match the method by op.
You can use $unset to remove feld, here is the example:
User.findOneAndUpdate(
{confirmationKey: req.params.key},
{isConfirmed: true, $unset: { confirmationKey: ""}}, {}, (err, doc) => {
if (err) {
res.status(HttpStatus.BAD_REQUEST).send(StatusGen.getError(err))
} else {
console.log('user = ' + JSON.stringify(doc))
res.send(StatusGen.getSuccess('confirmed'))
}
})
Now field confirmationKey is is deleted.

Object #<Promise> has no method 'limit'

when I run code
var collection = db.get('categories');
console.log(collection.find().limit(1).sort( { _id : -1 } ));
on nodejs using mongodb I am getting error Object # has no method 'limit' . I am a beginner to node and really stuck on this section of node
here is full code for geting last insert document.
router.post('/addcategory', function(req, res) {
// Set our internal DB variable
var db = req.db;
// Get our form values. These rely on the "name" attributes
var name = req.body.name;
var description = req.body.description;
// Set our collection
var collection = db.get('categories');
// Submit to the DB
collection.insert({
"name" : name,
"description" : description,
}, function (err, doc) {
if (err) {
// If it failed, return error
res.send("There was a problem adding the information to the database.");
}
else {
// And forward to success page
/******************/
console.log(collection.find().limit(1).sort( { _id : -1 } ));
/*************/
}
});
});
The key piece of missing information here was that you are using Monk, not the native MongoDB Node.JS driver. The command you have for find() is how you would use the native driver (with the changes suggested by #BlakesSeven above for asynchronity), but Monk works a little bit differently.
Try this instead:
collection.find({}, { limit : 1, sort : { _id : -1 } }, function (err,res) {
console.log(res);
});
The method is still asynchronous so you still need to invoke itm either as a promise with .then() or a callback. No methods are sychronous and return results in-line.
Also the result returned from the driver is s "Cursor" and not the object(s) you expect. You either iterate the returned cursor or just use .toArray() or similar to convert:
collection.find().limit(1).sort({ "_id": -1 }).toArray().then(function(docs) {
console.log(docs[0]);
});
Or:
collection.find().limit(1).sort({ "_id": -1 }).toArray(function(err,docs) {
console.log(docs[0]);
});
But really the whole premise is not correct. You seem to basically want to return what you just inserted. Event with the correction in your code, the returned document is not necessarily the one you just inserted, but rather the last one inserted into the collection, which could have occurred from another operation or call to this route from another source.
If you want what you inserted back then rather call the .insertOne() method and inspect the result:
collection.insertOne({ "name": name, "description": description },function(err,result) {
if (err) {
res.send("There was an error");
} else {
console.log(result.ops)
}
});
The .insert() method is considered deprecated, but basically returns the same thing. The consideration is that they return a insertWriteOpResult object where the ops property contains the document(s) inserted and their _id value(s),

MongoDB node native driver creating duplicate documents

I'm getting a duplicate document when using the mongodb-native-driver to save an update to a document. My first call to save() correctly creates the document and adds a _id with an ObjectID value. A second call creates a new document with a text _id of the original ObjectID. For example I end up with:
> db.people.find()
{ "firstname" : "Fred", "lastname" : "Flintstone", "_id" : ObjectId("52e55737ae49620000fd894e") }
{ "firstname" : "Fred", "lastname" : "Flintstone with a change", "_id" : "52e55737ae49620000fd894e" }
My first call correctly created Fred Flinstone. A second call that added " with a change" to the lastname, created a second document.
I'm using MongoDB 2.4.8 and mongo-native-driver 1.3.23.
Here is my NodeJS/Express endpoint:
app.post("/contacts", function (req, res) {
console.log("POST /contacts, req.body: " + JSON.stringify(req.body));
db.collection("people").save(req.body, function (err, inserted) {
if (err) {
throw err;
} else {
console.dir("Successfully inserted/updated: " + JSON.stringify(inserted));
res.send(inserted);
}
});
});
Here is the runtime log messages:
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone"}
'Successfully inserted/updated: {"firstname":"Fred","lastname":"Flintstone","_id":"52e55737ae49620000fd894e"}'
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone with a change","_id":"52e55737ae49620000fd894e"}
'Successfully inserted/updated: 1'
Why doesn't my second update the existing record? Does the driver not cast the _id value to an ObjectID?
What you are posting back the 2nd time contains a field named "_id", and it's a string. That is the problem.
Look at the document, what the save method does is a "Simple full document replacement function". I don't use this function quit often so here's what I guess. The function use the _id field to find the document and then replace the full document with what you provided. However, what you provided is a string _id. Apparently it doesn't equal to the ObjectId. I think you should wrap it to an ObjectId before passing to the function.
Besides, the save method is not recommended according to the document. you should use update (maybe with upsert option) instead
I don't exactly know why a second document is created, but why don't you use the update function (maybe with the upsert operator)?
An example for the update operation:
var query = { '_id': '52e55737ae49620000fd894e' };
db.collection('people').findOne(query, function (err, doc) {
if (err) throw err;
if (!doc) {
return db.close();
}
doc['lastname'] = 'Flintstone with a change';
db.collection('people').update(query, doc, function (err, updated) {
if (err) throw err;
console.dir('Successfully updated ' + updated + ' document!');
return db.close();
});
});
And now with the upsert operator:
var query = { '_id': '52e55737ae49620000fd894e' };
var operator = { '$set': { 'lastname': 'Flintstone with a change' } };
var options = { 'upsert': true };
db.collection('people').update(query, operator, options, function (err, upserted) {
if (err) throw err;
console.dir('Successfully upserted ' + upserted + ' document!');
return db.close();
});
The difference is that the upsert operator will update the document if it exist, otherwise it will create a new one. When using the upsert operator you should keep in mind that this operation can be underspecified. That means if your query does not contain enough information to identify a single document, a new document will be inserted.

Mongoose retrieving data without _id field

I would like to retrieve some data from a Mongoose setting in my Node.js application. I noticed that no matter what I write as field selection, I always get the _id field. Is there a way not to fetch it?
This is how I do right now:
Transaction.find({username : user.username}, ['uniqueId', 'timeout', 'confirmation_link', 'item_name'], function(err, txs){
console.log("user : " + user.username + " with txs: " + txs);
callback(txs);
});
And logs me the results which contain the _id field.
Another way is to use text argument with prefix - which will exclude this or that field from the result:
Entity.find({ ... }, '-_id field1 field2', function(err, entity) {
console.log(entity); // { field1: '...', field2: '...' }
});
_id must be specifically excluded. For example,
Transaction.find({username : user.username}, { '_id': 0, 'uniqueId' :1, 'timeout': 1, 'confirmation_link': 1, 'item_name': 1}, function(err, txs){
console.log("user : " + user.username + " with txs: " + txs);
callback(txs);
});
Another approach:
Augment the .toJSON() of the schema that it deletes the _id and the __v fields
Call .toJSON() on all DB objects sent to client
Extra benefit #1: you can use item.id === 'something' because typeof id === 'string', not ObjectId.
Extra benefit #2: When you got gan object back from the client and you want to search / update then you don't have to manually delete _id because there is none, just an id which is ignored.
Augmenting JSON:
mySchema.set('toJSON', {
virtuals: true,
transform: (doc, ret, options) => {
delete ret.__v;
ret.id = ret._id.toString();
delete ret._id;
},
});
So you can use:
let item = (await MyCollection.findOne({/* search */}).exec()).toJSON();
if (item.id === 'someString') return item;
I know it's ugly. But it's the best bad idea that I have so far.
In 5.2.13 version of Mongoose (Sept 2018)- using the query builder approach the same can be converted to
async function getUserDetails(user) {
try {
if (!user || !user.name) return;
const result = await Transaction.
find({username : user.username}).
select('uniqueId timeout confirmation_link item_name -_id');
// Adding minus sign before the _id (like -_id) in the select string unselects the _id which is sent by default.
console.log(result);
} catch(ex) {
return ex
}
}
The easiest thing you can do is something like this:
Transaction.find({username : user.username}, {_id: 0}, (err, txs) => {
// the return document won't contain _id field
// callback function body
}
Just remember that in the second object passed in the find()-
Pass 0 as the value to the specific key that you wish not to fetch
from the mongodb database.
Pass 1 as the value when you wish to
fetch from the mongodb database.

Resources