Issue: updating value inside the subdocument of a subdocument - node.js

I am having some trouble trying to update some fields of a subdocument inside a subdocument in mongodb.
First of all, let's find exactly the document I need to update to see how the document structure looks like:
// query:
db.getCollection('collection').find({
application: ObjectId("568b3a2feaa4171d03734776"),
_id: ObjectId("568b3a2feaa4171d03734779"),
status: 'sent',
'mailingList.recipients.email': 'example#example.com' // an index
},
{
'mailingList.$.recipients': true
});
// query result:
{
"_id" : ObjectId("568b3a2feaa4171d03734779"),
"mailingList" : [
{
"id" : 55,
"recipients" : [
{
"metadata" : {
"name" : "Example",
"surname" : "Example"
},
"email" : "example#example.com",
"message" : {
"events" : []
}
}
]
}
]
}
What I would like to achieve is exactly being able to update any of the fields in the object inside recipients[]: let's say, for example, email. I've tried the following so far, using the $set operator:
db.getCollection('collection').update({
application: ObjectId("568b3a2feaa4171d03734776"),
_id: ObjectId("568b3a2feaa4171d03734779"),
status: 'sent',
'mailingList.recipients.email': 'example#example.com'
},
{
$set: {
'mailingList.$.recipients.email': 'newmail#example.com'
}
});
but I get the following error:
cannot use the part (recipients of mailingList.1.recipients.email) to
traverse the element ({recipients: [ { metadata: { name: "Example",
surname: "Example" }, email: "example#example.com", message: { events:
[] } } ]})
What am I missing? I had already worked with embedded subdocuments before (not like this where there is a subdocument inside another subdocument) and using $set was enough to update any field inside a single subdocument, i.e:
$set: {
'mailingList.$.email': 'newmail#gmail.com'
}

Related

Prevent mongoose "Model.updateOne" from updating ObjectId(_id) of the model when using "$set"

I'm updating the age and name of a character with a specific _id from an array of characters that is inside a document of model Drama.
The document I'm working with:-
{
"_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
"characters" : [
{
"_id" : ObjectId("619fdac5a03c8b10d0b8b13c"),
"age" : "23",
"name" : "Vinay",
},
{
"_id" : ObjectId("619fe1d53810a130207a409d"),
"age" : "25",
"name" : "Raghu",
},
{
"_id" : ObjectId("619fe1d53810a130207a502v"),
"age" : "27",
"name" : "Teju",
}
],
}
So to update the character Raghu I did this:-
const characterObj = {
age: "26",
name: "Dr. Raghu",
};
Drama.updateOne(
{ _id: req.drama._id, "characters._id": characterId },
{
$set: {
"characters.$": characterObj,
},
},
function(err, foundlist) {
if (err) {
console.log(err);
} else {
console.log("Update completed");
}
}
);
// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")
This updated the character but it also assigned a new ObjectId to the _id field of the character. So, I'm looking for ways on how to prevent the _id update.
Also, I know I can set the individual fields of character instead of assigning a whole new object to prevent that but it will be very tedious if my character's object has a lot of fields.
//Not looking to do it this way
$set: {
"characters.$.age": characterObj.age,
"characters.$.name": characterObj.name,
},
Thanks.
I found something here, just pre define a schema (a blueprint in a way) that affects the id
var subSchema = mongoose.Schema({
//your subschema content
},{ _id : false });
Stop Mongoose from creating _id property for sub-document array items
Or I would say, when you create a character assign it a custom id from the start, that way it will retain that id throughout.
I'm leaving this question open as I would still like to see a simpler approach. But for now, I did find one easy alternative solution for this issue which I'm will be using for some time now until I find a more direct approach.
In short - Deep merge the new object in the old object using lodash and then use the new merged object to set field value.
For example, let's update the character Raghu from my question document:-
First install lodash(Required for deep merging objects) using npm:
$ npm i -g npm
$ npm i --save lodash
Import lodash:
const _ = require("lodash");
Now update the character Raghu like this:-
const newCharacterObj = {
age: "26",
name: "Dr. Raghu",
};
Drama.findById(
{ _id: req.drama._id, "characters._id": characterId },
"characters.$",
function(err, dramaDocWithSpecificCharacter) {
console.log(dramaDocWithSpecificCharacter);
// ↓↓↓ console would log ↓↓↓
// {
// "_id" : ObjectId("619d44d2ec2ca20ca0404b5a"),
// "characters" : [
// {
// "_id" : ObjectId("619fe1d53810a130207a409d"),
// "age" : "25",
// "name" : "Raghu",
// }
// ],
// }
const oldCharacterObj = dramaDocWithSpecificCharacter.characters[0];
const mergedCharacterObjs = _.merge(oldCharacterObj, newCharacterObj);
// _.merge() returns a deep merged object
console.log(mergedCharacterObjs);
// ↓↓↓ console would log ↓↓↓
// {
// _id: 619fe1d53810a130207a409d,
// age: "26",
// name: "Dr. Raghu",
// };
Drama.updateOne(
{ _id: req.drama._id, "characters._id": characterId },
{
$set: {
"characters.$": mergedCharacterObjs,
},
},
function(err, foundlist) {
if (err) {
console.log(err);
} else {
console.log("Update completed");
}
}
);
}
);
// req.drama._id is ObjectId("619d44d2ec2ca20ca0404b5a")
// characterId is ObjectId("619fe1d53810a130207a409d")
Note: We can also use the native Object.assign() or … (spread operator) to merge objects but the downside of it is that it doesn’t merge nested objects which could cause issues if you later decide to add nested objects without making changes for deep merge.
You can pass your payload or request body like this if we provide _id it will prevent update to nested document
"characters" : [
{
"_id" : "619fdac5a03c8b10d0b8b13c",
"age" : "updated value",
"name" : "updated value",
}, {
"_id" : "619fe1d53810a130207a409d",
"age" : "updated value",
"name" : "updated value",
}, {
"_id" : "619fe1d53810a130207a502v",
"age" : "updated value",
"name" : "updated value",
}
],
It works for me for bulk update in array object

MongoDB update multiple items in an array of objects with corresponding data

I have an array in my MongoDB document as shown below:
{
...otherDocFields,
groupID: "group-id",
users: [
{id: "uid1", name: "User1"},
{id: "uid2", name: "User2"},
{id: "uid3", name: "User3"},
{id: "uid4", name: "User4"}
]
}
I'm trying to write a function that will update users' names based on that ID.
I tried something like:
async function updateNames(groupID: string, data: Array<{id: string, name: string}>) {
try {
// MongoDB Aggregation
await mongoDB.collection("users").aggregate([
{$match: {groupID}},
{$unwind: {
path: '$users',
includeArrayIndex: 'users.id',
preserveNullAndEmptyArrays: true
}
}
//....
])
} catch (e) {
console.log(e)
}
}
I'm stuck at the part to update the relevant names from the data param in the function.
A sample data would be:
[
{id: "uid1", name: "newName1"},
{id: "uid3", name: "newName3"}
]
I can for sure read, manually process it and update the document but I'm looking for a way to do it in single go using aggregation.
You can do this with an update statement using array filters (https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#---identifier--)
First, if we just declare some tests data which would be passed into your method:
const data = [
{id: "uid2", name: "ChangeName1"},
{id: "uid4", name: "ChangedName2"}
];
We can then create an update statement which updates all the users within that list within a map and a reduce:
const sets = data.map((element, index) => ({ [`users.$[f${index}].name`]: element.name })).reduce((accumulator, currentValue) => (Object.assign(currentValue, accumulator)), { });
const update = { $set: sets };
This will give us the following update statement:
{
"$set" : {
"users.$[f1].name" : "ChangedName2",
"users.$[f0].name" : "ChangeName1"
}
}
We can create a bunch of array filters with that data:
const arrayFilters = data.map((element, index) => ({ [`f${index}.id`]: element.id }));
This will give us the following which we can pass into the options of an update.
[ { "f0.id" : "uid2" }, { "f1.id" : "uid4" } ]
Last of all we can execute the following update command:
db.users.updateOne(
filter,
update,
{ arrayFilters }
);
Now if we check the output we'll get the following results
> db.users.find().pretty()
{
"_id" : ObjectId("60e20841351156603932c526"),
"groupID" : "123",
"users" : [
{
"id" : "uid1",
"name" : "User1"
},
{
"id" : "uid2",
"name" : "ChangeName1"
},
{
"id" : "uid3",
"name" : "User3"
},
{
"id" : "uid4",
"name" : "ChangedName2"
}
]
}

MongoDB add field to an object inside an array

I have an object user that looks like that
{
"_id" : ObjectId("5edbdf57ac52325464b054ec"),
...
"purchaseHistory" : [
{
"_id" : ObjectId("5ee7a8f6b438a1254cec3f74"),
...
},
{
"_id" : ObjectId("5ee7a8f6b438a1254cec3f88"),
...
}
]
}
What I wanna do is to add a new field to a specific object inside "purchaseHistory" by ID, for example I wanna add to "5ee7a8f6b438a1254cec3f88" a field "status": 0
What I tried is
users.findOneAndUpdate(
{
_id: ObjectId(userId),
'purchaseHistory._id': ObjectId(saleId)
},
{
$set: { 'purchaseHistory.$.status': status}
}
)
But it gives me an error, how can I do it properly?
According to the website provided by D. SM, I was able to do it this way
users.findOneAndUpdate(
{
'_id': ObjectId(userId),
'purchaseHistory._id': ObjectId(saleId)
},
{
$set: { 'purchaseHistory.$.status': status }
}
)
MongoDB provides the positional update operator for cases like this.

Unable to push an item into a MongoDB array within the document while using Waterline

I'm struggling with an update call that just doesn't seem to work for some reason. For some quick context, we have a Node.js application running on Sails.js with Waterline ORM.
As per my package.json file, here are the versions I am using:
connect-mongo: ^1.3.2
mongodb: ^2.2.29
sails: ~0.12.4
sails-mongo: ^0.12.3
I have a collection called "products" and each product looks like this in the database:
{
"_id" : ObjectId("59d5f12025423dc0261c911d"),
"category" : ObjectId("59a9bcf984d756998eaa22e5"),
"status" : "pendingReview",
"isDeleted" : false,
"events" : [
{
"when" : 1507193120,
"actor" : "56cc0f76e1a25cde0d2c15ab",
"action" : "Submitted product",
"note" : "Jeff added this product. It is awaiting review."
}
],
"productId" : "171005-00000",
"createdAt" : ISODate("2017-10-05T08:45:20.538Z"),
"updatedAt" : ISODate("2017-10-05T08:45:20.538Z")
}
I have a UI where a user can "approve" multiple products before they are displayed to website visitors. To do this, I want to just update multiple records by changing the status key to "approved" and adding an event in the events array of each document. The event must be in position 0 of the array. I am trying to update these records with the following code but it doesn't seem to work:
var moment = require('moment');
Products.native(function(error, collection) {
if (error) {
throw error;
}
collection.update(
{ _id: ['59d5f12025423dc0261c911d'] },
{
$set: {
status: 'approved',
$push: {
events: {
when: moment().unix(),
actor: req.body.userId,
action: 'Approved product',
note: req.body.userName + ' approved this product.'
}
}
}
},
{ multi: true },
function(error, count, status) {
if (error) {
sails.log.error(error);
return res.serverError('Database error. Reference ID: ' + req.referenceId);
}
return res.ok(count);
});
});
When I run this query, I don't get any error and when I check my database, the record has not been updated. I get the following data when I run the query:
{
"ok": 1,
"nModified": 0,
"n": 0
}
What's going on here? Why isn't getting updated? If I understand correctly, the query is able to find the document but it isn't getting updated. Is that correct? If yes, how do I resolve this? Thanks.
$push should be at the same level as $set. They are both operators...
This means you should change like this:
{
$set: {
status: 'approved'
},
$push: {
events: {
when: moment().unix(),
actor: req.body.userId,
action: 'Approved product',
note: req.body.userName + ' approved this product.'
}
}
}

$pulling an object from an array based on _id in Mongoose [duplicate]

Doc:
{
_id: 5150a1199fac0e6910000002,
name: 'some name',
items: [{
id: 23,
name: 'item name 23'
},{
id: 24,
name: 'item name 24'
}]
}
Is there a way to pull a specific object from an array? I.E. how do I pull the entire item object with id 23 from the items array.
I have tried:
db.mycollection.update({'_id': ObjectId("5150a1199fac0e6910000002")}, {$pull: {id: 23}});
However I am pretty sure that I am not using 'pull' correctly. From what I understand pull will pull a field from an array but not an object.
Any ideas how to pull the entire object out of the array.
As a bonus I am trying to do this in mongoose/nodejs, as well not sure if this type of thing is in the mongoose API but I could not find it.
try..
db.mycollection.update(
{ '_id': ObjectId("5150a1199fac0e6910000002") },
{ $pull: { items: { id: 23 } } },
false, // Upsert
true, // Multi
);
I have a document like
I have to delete address from address array
After searching lots on internet I found the solution
Customer.findOneAndUpdate(query, { $pull: {address: addressId} }, (err, data) => {
if (err) {
return res.status(500).json({ error: 'error in deleting address' });
}
res.json(data);
});
my database:
{
"_id" : ObjectId("5806056dce046557874d3ab18"),
"data" : [
{ "id" : 1 },
{ "id" : 2 },
{ "id" : 3 }
]
}
my query:
db.getCollection('play_table').update({},{$pull:{"data":{"id":3}}},{multi:true}
output:
{
"_id" : ObjectId("5806056dce046557874d3ab18"),
"data" : [
{ "id" : 1 },
{ "id" : 2 }
]
}
You can try it also:
db.getCollection('docs').update({ },{'$pull':{ 'items':{'id': 3 }}},{multi:true})
For a single record in array:
db.getCollection('documents').update(
{ },
{'$pull':{ 'items':{'mobile': 1234567890 }}},
{new:true}
);
For a multiple records with same mobile number in array:
db.getCollection('documents').update(
{ },
{
$pull: {
items: { mobile: 1234567890 }
}
},
{ new:true, multi:true }
)
Use $pull to remove the data
return this.mobiledashboardModel
.update({"_id": args.dashboardId}, { $pull: {"viewData": { "_id": widgetId}}})
.exec()
.then(dashboardDoc => {
return {
result: dashboardDoc
}
});
Kishore Diyyana:
If you want to remove all elements including the key of the element attributes list.
Here is the example of mongoDB unset operator:
db.UM_PREAUTH_CASE.update(
{ 'Id' : 123}, { $unset: { dataElements: ""} } )
JSON Look like this:
{ "Id":123,"dataElements" : [ { "createdBy" : "Kishore Babu Diyyana", "createdByUserId" : 2020 }, { "createdBy" : "Diyyana Kishore", "createdByUserId" : 2021 } ] }

Resources