MongoDB, removing a specific object with one value name from an array - javascript

Please pardon me for my bad explanation, I'm very new to coding. (discord bot)
So I have an array with 2 required values (character name & shards) and 1 optional value (character image link)
It's basically a function where each user can have multiple 'characters' and I want to remove one character using just the name of the character as an input, from that user's data when a command is run.
This is my data right now:
{
"_id" : ObjectId("62593f2380fea1cee9f986ed"),
"userID" : "321311458459779073",
"characters" : [
{
"characterName" : "Akasuka",
"characterImg" : "",
"shards" : "0"
},
{
"characterName" : "Nigeria",
"characterImg" : "",
"shards" : "0"
}
],
"__v" : 3
}
My code to remove it:
(prof = database that I required at the top using const prof = require("../../Schemas/ProfileDB");)
(cName = input from the command)
prof.findOne({ userID: pUser.id }, async (err, data) => {
if (err) throw err;
if (data) {
const id = data._id
prof.updateOne(
{ userID: pUser.id },
{ $pull: {'characters': {characterName: cName}}},
{ safe: true }
)
data.save()
}
})
And, well, as you might have guessed it does not remove the object from the array.

Related

If value of a property is null when updating then that property should not be added to the record

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

MongoDB- Return Array of existing fields by compare collection with Array of object

I'm new to Mongo and trying compare a array with a documents of collections and return list of matching records.
Let me explain:First Array
I have a collection (user) with following documents:
> db.user.find().pretty()
{
"_id" : ObjectId("57358220bf3e7d076b6ccdb1"),
"name" : "Sunny",
"phone" : "9417702107",
"email" : "ssdhiman07#gmail.com"
}
{
"_id" : ObjectId("57358242bf3e7d076b6ccdb2"),
"name" : "Pal",
"phone" : "9015719419",
"email" : "ss998#gmail.com"
}
{
"_id" : ObjectId("57358262bf3e7d076b6ccdb3"),
"name" : "viveky",
"phone" : "8826565107",
"email" : "sds998#gmail.com"
}
Second Array: i have a array of objects that is come from Http request below is structure of array.
{
"contacts" : [
{
"name" : "Sunny",
"phone" : "9417702107"
},
{
"name" : "Sukhpal",
"phone" : "9015719419"
},
{
"name" : "anurag",
"phone" : "9988776655"
},
{
"name" : "vivek",
"phone" : "8826565107"
}
]
}
Now I want to know which objects of Second Array are exists in First Array and which doesn't . Comparison should on basis of phone only . And in result i want same Array as Second Array but with one extra field that is
"exists":"true" or "exists":"false" . Something like this.
{
"contacts" : [
{
"name" : "Sunny",
"phone" : "9417702107"
"exists" :"true"
},
{
"name" : "pal",
"phone" : "90177668899"
"exists" :"false"
}
]
}
So for this i had tried something here is code of node.js with mongoos.
exports.matchcontacts = function(req, res, next)
{
var response = {};
var conArray = req.body.contacts;
var contact_list = [];
for(var i=0; i<conArray.length;i++)
{
var name = conArray[i].name;
var phone = conArray[i].phone;
Users.findOne({"phone":conArray[i].phone},function(err,data)
{
if(err)
{
response = {"error" : true,"message" : "Error fetching data"};
}
else if(!data)
{
contact_list.push({name:name,phone:phone,exists:"false"});
}
else
{
contact_list.push({name:name,phone:phone,exists:"true"});
}
});
}
response = {"error":false,"contacts":contact_list};
res.json(response);
};
But always got null {} empty result, and if i tried to get response inside Callback function then it return only single last compared value.
Problem is in first method is that callback function return result very late so result always empty .
and in second method loop override result and it also inefficient to use callback inside loop it will call no of time. So whole story i had explained
Now Please can anybody help me with code or suggest right way to get desired result thanks
HavenĀ“t used mongodb, but the idea I would use is to first iterate your contacts and make a mapping of the phones to the corresponding object and mark them as non existent:
var obj = {};
for(var i=0; i<conArray.length;i++){
obj[conArray[i].phone] = conArray[i];
conArray[i].exists = false;
}
Then search in some way for the users that have those phones in your db, something like
var results = Users.find(records that have phone in Object.keys(obj) array)
Finally, you iterate your existant records and mark the corresponding contact
for(var i=0;i<results.length;i++){
obj[results[i].phone].exists = true;
}

MongoJS add to existing array

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);
});

Mongo doesn't accept insertion of duplicate usernames

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.

Mongodb delete subdocument

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 } } },
});

Categories

Resources