I am trying to add to an existing array in my mongoDB. Here is what I have, but it is of course incorrect because all the data gets wiped out after it tries to add it:
db.cardKeeper.update(
{_id: ObjectId('5621c5ac30895e5776e4d1ea')},
{
$push:{'cardKeeperApp.appData.cardDecks':deckObject}
}
)
deckObject which is the object I am trying to add to the array looks like this
var deckObject = {
name: productName,
searchName: productItem,
price:{
purchasePrice: productCost,
averageWorth: priceAverageFixed,
lowWorth: lowestSoldAmount,
highWorth: highestSoldAmount
}
}
and as you can see cardKeeperApp.appData.cardDecks is my array which holds more info that looks just like deckObject
If it helps here is the full object, I am trying to add to the cardDecks array. I have marked fields as null
{
"_id" : ObjectId("5635ddf82f4c220f4f932af2"),
"cardKeeperApp" : {
"appData" : {
"cardDecks" : [
{
"name" : "Some Name",
"searchName" : "Some+Name",
"price" : {
"purchasePrice" : null,
"averageWorth" : null,
"lowWorth" : null,
"highWorth" : null
}
}
],
"allDeckTotalWorth" : null
}
}
}
I found the solution. findAndModify by query:{_id:ObjectId} and then update and push:
db.cardKeeper.findAndModify({query:{_id: ObjectId('5635ddf82f4c220f4f932af2')},
update: {$push:{'cardKeeperApp.appData.cardDecks': deckObject}},
new:true}, function (err, doc) {
res.json(doc);
});
Related
I'm trying to update a value in an array
{
_id: "5d0b939f1f0cc14f83153c43"
categoryName: "Email"
client: "5cff60a4b8b1490049e8325b"
notes: ["note to be updated"]
}
const updateNoteInNoteCategory = async (Id, newNote, index) => {
const updatedNote = await Note.findByIdAndUpdate(
{ _id: Id },
{ $set: { notes[index]: newNote } },
{ new: true }
);
}
I'm passing the index of field to be updated, but I can't seem to get it to work.
any help would be appreciated.
You should try passing the index by the dot notation like:
{$set: {notes.index : newNote}}
And please take a look to this.
If I got it, I think while you using { notes[0]: newNote }, you may get { 'note to be updated': newNote }. (I'v tried, it's invalid)
Maybe you should try { 'notes.0': newNote }.
You need to use $ to get the correct element index inside notes, and pass some parameter to find the element you want to update in the find, here's an exemple
Notes.updateOne({
_id: Id,
'notes': 'note to be updated'
}, {
$set: {
'notes.$': 'note updated'
}
})
You can try passing the dot notation for array.
db.arraycheck.update({},{$set: { "notes.0":"checking after updation" }})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
db.arraycheck.find()
{ "_id" : "5d0b939f1f0cc14f83153c43", "categoryName" : "Email", "client" : "5cff60a4b8b1490049e8325b", "notes" : [ "checking after updation" ] }
Suppose I have a mongoose schema like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var testSchema = new Schema({
name: {type: String, required: true},
nickName: {type: String}
});
var Test = module.exports = mongoose.model('Test', testSchema);
I declare methods for CRUD operation using variable Test. From that one such method is update, which is defined as follows:
module.exports.updateTest = function(updatedValues, callback) {
console.log(updatedValues); //this output is shown below
Test.update(
{ "_id": updatedValues.id },
{ "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
{ multi: false },
callback
);
};
Now, I use this method inside my node router as follows:
router.put('/:id', function(req, res, next) {
var id = req.params.id,
var name = req.body.name,
var nickName = req.body.nickName
req.checkBody("name", "Name is required").notEmpty();
var errors = req.validationErrors();
if(errors) { ........ }
else {
var testToUpdate = new Test({
_id: id,
name: name,
nickName: nickName || undefined
});
Test.updateTest(testToUpdate, function(err, result) {
if(err) { throw(err); }
else { res.status(200).json({"success": "Test updated successfully"}); }
});
}
});
Now if I save a new record in database and then see it in database then it looks like:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id
"name" : "firstTest",
"__v" : 0
}
Now if I update the same document without changing anything and then if I take a look at same record in database, then I get:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id
"name" : "firstTest",
"__v" : 0,
"nickName" : null
}
Can you see that nickName is set to null? I don't want it to work like this. I want that if my property is null, then that property should not be included in the record.
If you remember, I have console logged the updatedValues before updating it. (see the second code block in question). So, here is the logged values:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id
"name" : "firstTest"
}
I don't know why, but nickName is not present in the logged values and then after update I get nickName: null. I think, the problem lies in second Code block. Can you please check it?
Note:
Actually I have lot more fields in my schema than I specified in question. Some fields are reference to other records as well.
You can prevent such documents from updating in MongoDB by setting the runValidators option to true in the update method.
Ex:
module.exports.updateTest = function(updatedValues, callback) {
Test.update(
{ "_id": updatedValues.id },
{ "$set" : {
"name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
},
{ multi: false, runValidators: true },
callback
);
};
In addition, you can also set the option omitUndefined to true to prevent undefined values from being reflected.
Ex:
module.exports.updateTest = function(updatedValues, callback) {
Test.update(
{ "_id": updatedValues.id },
{ "$set" : {
"name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
},
{ multi: false, runValidators: true, omitUndefined: true },
callback
);
};
I wouldn't write this this way, but I'll tell you why your code is failing.
The problem is your $set block
You're choosing to specifically set the value to the update object passed in. If the value is undefined you're forcing mongo to set that to null.
Here's the problem
example, in DB:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"),
"name" : "firstTest",
"nickname": "jack",
"__v" : 0
}
IF you pass in testToUpdate = { name: 'foo' } you'll end up with
Test.update({ ... }, { $set: { name: 'foo', nickname: undefined }}
because you're getting updatedValues.nickname off of the arguments and thats not defined
What you want is
Test.update({ ... }, { $set: updatedValues }
which is translated to
Test.update({ ... }, { $set: { name: 'foo' } }
You're no longer providing a key for nickname, thus not making it set to undefined/null.
I would use a mongoose plugin and not worry about manually passing the fields all the way to your model (see github.com/autolotto/mongoose-model-update)
You can define the update-able fields and then you can just do model.update(req.body) and not worry about all this
Even if you don't want to use the plugin you can still just do Test.findByIdAndUpdate(id, { name, nickname }, callback)
Its true that the problem is your $set part as pointed in the other answers, but using if condition is not the best way to do this. What if there are multiple fields you might need multiple if conditions or even a separate function to deal with this.
Mongoose offers a really good option to deal with this:{omitUndefined: 1} , it will not update all the undefined values in your query.
Taking a look at your code, there is a problem in your update method that won't help you to obtain the result you want.
This is you update code:
module.exports.updateTest = function(updatedValues, callback) {
console.log(updatedValues); //this output is shown below
Test.update(
{ "_id": updatedValues.id },
{ "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
{ multi: false },
callback
);
};
The problem is in the $set part.
In mongodb you cannot unset a field in a document by just assigning undefined to it. You should use the $unset operator.
So your update code should be something like:
module.exports.updateTest = function(updatedValues, callback) {
console.log(updatedValues); //this output is shown below
const operators = {$set: {name: updatedValues.name}};
if (updatedValues.nickName) {
operators.$set.nickName = updatedValues.nickName;
} else {
operators.$unset = {nickName: 1};
}
Test.update(
{ "_id": updatedValues.id },
operators,
{ multi: false },
callback
);
};
Note that the use of $unset is fundamental to remove the field if it already exists in your document.
As you're using $set in the query which says in the documentation
The $set operator replaces the value of a field with the specified value.
So you're forcing to set the undefined value which is null here to the field. Either you set required : true against the nickName field in the schema or try passing JS object instead of writing raw query like,
Instead of :
Test.update(
{ "_id": updatedValues.id },
{ "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
{ multi: false },
callback
);
Try doing:
var data = { name : updatedValues.name };
if(updatedValues.nickName){
data.nickName = updatedValues.nickName;
}
Model.update({ "_id": updatedValues.id }, data ,options, callback)
Check this link in Mongoose documentation for more information on the approach.
The problem is that you are still adding the nickname property on the document - you're just setting it to undefined, but contrary to what you thought that's not the same as not setting it at all.
What you want is to not include the nickname property at all if it's undefined, which you can do like this:
module.exports.updateTest = function(updatedValues, callback) {
const set = { "name": updatedValues.name };
if (updatedValues.nickName) {
set["nickName"] = updatedValues.nickName;
}
Test.update({ "_id": updatedValues.id }, { "$set": set}, { multi: false },
callback
);
};
The easiest method I found is by using lodash.pickby you just pass body (or any object for that matter) and it removes all undefined and null fields and keys
{
"accounts" : {
"-account1" : {
"email" : "test#example.com",
"name" : "John doe",
"online" : false,
"profilePic" : "example.com/img.png",
"username" : "jonh_doe"
"tokens" : [
"token11111",
"token22222",
"token33333"]
},
"-account2" : {
"email" : "testymctest#example.com",
"name" : "Jane doe",
"online" : false,
"profilePic" : "example.com/img.png",
"username" : "jane"
"tokens" : [
"token44444",
"token55555",
"token66666"]
}
}
}
My user data is structured as above and I'm trying to determine if -account1 has a token with the value "token11111"
Other Firebase examples suggest using snapshot, but I haven't found an example that drills down into a child element to find a value.
This is what I've tried
firebase.database().ref('accounts/-account1/tokens')
.equalTo(newToken)
.once('value')
.then(function(tokens) {
if (tokens.exists()) {
//Token already exists
}
else{
//Push new token to db
}
});
When you get an array-like object back from a Firebase snapshot (the keys are numbers starting a 0), you deal with it just like a regular JavaScript array:
var ref = firebase.database().ref('/accounts/-account1/tokens');
ref.once('value').then(function(snap) {
var array = snap.val();
for (var i in array) {
var value = array[i]
console.log(value);
if (value == 'whatever') { ... }
}
});
This will iterate and print each value. You can look for any value that you like in that loop. Or you can includes(x) on the array in JavaScript ES2016.
I need mongo to accept duplicate usernames because it is possible that you might have the same username for a different site, I have my code like this.
_(data)
.forEach(function (a, key) {
jobs['insert_' + a] = function (callback) {
mongo.open(config.MONGO_DB)
.collection('usernames')
.insert({
username: a,
indic: key,
favorites: ''
}, function (err, result) {
if (err) {
return next(err);
}
callback();
});
};
}
.commit();
async.parallel(jobs, send_response);
and I only get this one line as a result
{ "_id" : ObjectId("5592724901c01a6ca6b76a6a"), "username" : "asd", "indic" : "twitch", "favorites" : "" }
and the data I am passing is:
data = {twitch: 'asd', hitbox: 'asd', dailymotion: 'asd'}
Shouldn't I have something like this?
`{ "_id" : ObjectId("5592724901c01a6ca6b76axx"), "username" : "asd", "indic" : "twitch", "favorites" : "" }
{ "_id" : ObjectId("5592724901c01a6ca6b76axx"), "username" : "asd", "hitbox" : "twitch", "favorites" : "" }
{ "_id" : ObjectId("5592724901c01a6ca6b76axx"), "username" : "asd", "dailymotion" : "twitch", "favorites" : "" }
I am using this as my async function.
Your a variable is the one that is duplicated, so you keep adding the same key to jobs (namely insert_asd).
If you don't necessarily need to refer to the specific insert actions in the results, you can make jobs an array instead of an object:
var jobs = _(data).map(function(a, key) {
return function(callback) {
....
};
}).value();
However, it may be possible with your MongoDB driver to add the documents in one go, instead of separately. The official MongoDB driver supports arrays of documents using insertMany(), for instance.
I'm trying to delete a subdocument in mongoDb but can't succeed in it. I've tried with $update,$push and $pull but I'm not successful.
I have the following document:
db.users.findOne({"_id": ObjectId("545677a9e4b0e0ef9379993c")})
{
"_class" : "com.xxx.xxx.server.da.User",
"_id" : ObjectId("545677a9e4b0e0ef9379993c"),
"bookSummaries" : [
{
"_id" : ObjectId("545677e7e4b0e0ef9379993d"),
"isbn" : "2746969644"
"title": "test title"
},
{
"_id" : ObjectId("546a522a44ae4f5e652fdca7"),
"loanSummaries" : [
{
"loanStatus" : "REQUESTED",
"loanId" : "5473992044aec466a619290c"
},
{
"loanStatus" : "REQUESTED",
"loanId" : "5473997d44aec466a619290d"
},
{
"loanStatus" : "REQUESTED",
"loanId" : "547605a744ae0f0d558ae757"
}
],
"testArray" : [
{
"title" : "Back to the future",
"id" : "test"
},
{
"title" : "Back to the future",
"id" : "test2"
},
{
"title" : "Back to the future",
"id" : "test3"
},
"test ",
"test ",
"test 2",
"test 2",
{
"loanStatus" : "REQUESTED"
}
],
"title" : "Back to the future"
}
]
}
and I'm trying to create the queries to:
delete the whole "testArray" subdocument for only this specific subdocument
delete a specific loanSummaries give its loanId for this specific subdocument
Can you help me creating those queries? I've seen several post like this one, but it is not the same problem.
Thanks a lot
I recently had the same problem where I had to remove a nested subarray. In the query you need to identify the exact object that you want to remove, and then in your update query you can use the $ sign as index for the object in the array that you want to pull.
var query = {
"_id" : ObjectId(someUserId),
"bookSummaries._id" : ObjectId(bookId), //might also work with isbn as long as it is uniq
"bookSummaries.loanSummaries.loanId" : loanId
};
var updateQuery = {
"$pull" : {
"bookSummaries.$.loanSummaries" : {
"loanId": loadId
}
}
}
Update
I tried the following queries in my shell which worked fine. You will have to enter the appropirate ids.
var query = { "_id" : ObjectId("53b73b1108fe927c0e00007f"), "bookSummaries._id" : ObjectId("53b73b1108fe927c0e00007f"), "bookSummaries.loanSummaries.loanId" : "53b73b1108fe927c0e00007f" }
var updateQuery = { "$pull" : { "bookSummaries.$.loanSummaries" : { "loanId": "53b73b1108fe927c0e00007f" } } }
Update 2
If you already know the index of item/object you want to remove you can use the queries below in order to achieve this.
Find the document you want to edit
var query = {
"_id" : ObjectId(someUserId),
};
Unset the object you want to remove
var update1 = {
"$unset" : {
"bookSummaries.1.loanSummaries.2" : 1
}
}
Pull the object which has the value null.
var update2 = {
"$pull" : {
"bookSummaries.1.loanSummaries" : null
}
}
And to remove you can basically use the same queries.
Find the document you want to edit
var query = {
"_id" : ObjectId(someUserId),
};
Unset the object you want to remove
var update1 = {
"$unset" : {
"bookSummaries.0.testArray" : 1
}
}
Because MongoDB provides a full JavaScript environment you can easily manipulate the document and save the data back to the db.
*Note: if you are using MongoDB with php or python etc. there are equivalent ways to do that.
1.to delete whole "testArray" subdocument for only this specific subdocument for example index(1) do
db.users.find({"_id": ObjectId("545677a9e4b0e0ef9379993c")}).forEach(function (user) {
delete user.bookSummaries[1].testArray;
//Saveing the changes
db.users.save(user);
});
to delete a specific loanSummaries give its loanId for this specific subdocument for example id 5473992044aec466a619290c do
db.users.find({"_id": ObjectId("545677a9e4b0e0ef9379993c")}).forEach(function (user) {
var loanToDelete = "5473992044aec466a619290c";
var loanIndexToDelete = -1;
var i;
for(i in user.bookSummaries[1].loanSummaries) {
if(user.bookSummaries[1].loanSummaries[i].loanId === loanToDelete) {
loanIndexToDelete = i;
break;
}
}
if (loanIndexToDelete > -1) {
delete user.bookSummaries[1].loanSummaries[loanIndexToDelete];
}
//Saving the changes
db.users.save(users);
});
I had to do something similar but delete multiple subdocuments instead of just one. My query included an array of ids and this worked for me:
User.updateOne(
{ _id: userId },
{
$pull: { bookSummaries: { _id: { $in: ids } } },
});