unable to load nested object data in mongoose database properly - javascript

The issue I am facing is that even though I am able to load the data in the database but the desired result is different from what is being returned.
The issue is that whenever I am running for loop the qn variable is inserted twice but need it only once.
Please if someone can help me resolve this issue.
userProject.js
router.post('/notification/check/upload/survey' , async (req,res) => {
//const taginsert = [];
const mcq = [];
//const newData = {};
//const o = {};
console.log(req.body);
for(var i=0; i<=req.body.questions.length-1;i++) {
// newData = {
// qn: req.body.questions[i].qn
// }
for(var j=0; j<=req.body.questions[i].options.length-1;j++){
const o =
{
qn: req.body.questions[i].qn,
//{
options: [{
option: req.body.questions[i].options[j].option,
totalValues: req.body.questions[i].options[j].totalValues
}]
}
// newData.options = o;
// newData.push(o);
mcq.push(o);
}
}
//mcq = newData;
const check = new userCampaignSurvey({
userId: req.body.userId,
status: 'Pending',
notification_type:req.body.notification_type,
questions: mcq
})
check.save((err,p) => {
if(err) {
res.status(500).send(err);
}
res.status(201).send(p);
})
})
model.js
userId:
{
type: Number,
required: true,
trim: true
},
notification_id:
{
type: Number,
},
notification_type:
{
type: Number,
required: true,
},
status:
{
type: String,
required: true
},
questions: [
{
qn: {
type: String,
required: true
},
options: [
{
option: {
type: String
},
totalViews: {
type:Number
}
}
]
},
],
created_at: {
type: Date,
required: true,
default: Date.now(),
},
});
result received
{
"userId": 1,
"notification_type": 1,
"status": "Pending",
"questions": [
{
"qn": "Which brand do you like better for buying shoes?",
"options": [
{
"option": "Adidas",
"_id": "639064c9ec215f2cd1b2b15d"
}
],
"_id": "639064c9ec215f2cd1b2b15c"
},
{
"qn": "Which brand do you like better for buying shoes?",
"options": [
{
"option": "Reebok",
"_id": "639064c9ec215f2cd1b2b15f"
}
],
"_id": "639064c9ec215f2cd1b2b15e"
}
],
"created_at": "2022-12-07T10:02:48.442Z",
"_id": "639064c9ec215f2cd1b2b15b",
"__v": 0
}
expected result
{
"userId": 1,
"status": "Pending",
"notification_type": 1,
"questions": [
{
"qn": "Which brand do you like better for buying shoes?",
"options": [
{
"option": "Adidas",
"totalViews": 20
},
{
"option": "Reebok",
"totalViews": 10
}
]
}
]
}

Related

i have objects in array in mongo db i want to delete all the objects but always left one object with{ id:_oldxxxxxxxx}

I have some code in javascript with moongooes that I used in mongo DB to store a data
Sometimes I need to delete all the objects in array
and get a clean array
this is my schema
const orderSchema =new Schema({
date: {
type: Date,
},
OrderNumber: {
type: String,
required: true
},
City: {
type: String,
required: true
},
Address: {
type: String,
required: true
},
Phone: {
type: String
},
Country: {
type: String
},
Name: {
type: String,
required: true
},
Trackingnumber: {
type: String
},
ZipCode: {
type: Number
},
Province: {
type: String,
},
fulfillmentOrders:{
type: String,
},
Quantity: {
},
});
Holde:[
orderSchema
],
module.exports = mongoose.model('User', userSchema);
and my data on mongo looks like this
"Holde": [
{
"OrderNumber": "gid://shopify/Order/4958122475753",
"City": "xxxx",
"Address": "xxxx",
"Phone": "",
"Country": "xxx",
"Name": "xxx",
"Trackingnumber": "0",
"ZipCode": xxxx,
"fulfillmentOrders": "gid://shopify/FulfillmentOrder/6034089509097",
"Quantity": [
{
"quantity": 1,
"product": {
"id": "gid://shopify/Product/7909915590889"
},
"variant": {
"sku": "11111"
}
}
],
"_id": {
"$oid": "6389b12faaade0788141bf4f"
}
I try to delete all the objects in my array
whit this code
const User = require('../model/User');
const foundUse= await User.findOne({ "user":req.body.user}).exec();
await foundUse.updateOne({
Holde :{
$pull: {'_id':6389b12faaade0788141bf4f},
}
},
)
and expect to get "hold":[]
but actually
I get
"Holde": [
{
"_id": {
"$oid": "6389d882afbc458cc1c1af23"
}
}
],
It's pretty normal because you are updating your User with theses data.
With mongoose, the way to propperly delete item is .deleteMany(), but in your case it will only delete the User (and you seems to want to delete only the order).
You can then filter user's orders and re-assign it without the found order, using FindOneAndUpdate, like:
const User = require('../model/User');
const foundUser = await User.findOne({ "user": req.body.user });
const result = await User.updateOne({
Holde: foundUser?.Holde.filter(holde => holde._id !== HOLD_ID_TO_DELETE)
});
Where HOLD_ID_TO_DELETE is the id to delete (you seems to pass the whole user object with all orders at the moment)
But not it would be prettier and more maintenable to create an Order collection, linked to your User one using ref.

Combine duplicate tokens inside huge JSON file into nested array of objects using React

I looked at several of the suggested solutions but none seemed to rise to this confounding data formatting challenge.
I have a huge JSON file (over 100k rows) and massive duplicates of data all as top level objects. Here's an example:
[
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326B",
"chipset":"Mediatek MT6853V/NZA",
"date":"2022-01-01",
"fw_id":"A326BXXS4AVA1",
"android":"R(Android 11)",
"known_passcode":false,
"afu":false,
"bfu":false,
"bruteforce":false
},
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326U",
"chipset":"Mediatek MT6853V/NZA",
"date":"2021-03-01",
"fw_id":"A326USQU1AUD4",
"android":"R(Android 11)",
"known_passcode":true,
"afu":false,
"bfu":true,
"bruteforce":true
},
{
"manufacturer":"Samsung",
"device":"Galaxy A32 5G",
"model":"SM-A326U1",
"chipset":"Mediatek MT6853V/NZA",
"date":"2021-09-01",
"fw_id":"A326U1UEU5AUJ2",
"android":"R(Android 11)",
"known_passcode":true,
"afu":false,
"bfu":true,
"bruteforce":true
},
{
"manufacturer":"LGE",
"device":"LG K31",
"model":"LGL355DL",
"chipset":"Mediatek MT6762",
"date":"unknown",
"fw_id":"L355DL10l",
"android":"unknown",
"known_passcode":false,
"afu":false,
"bfu":false,
"bruteforce":false
}
]
This needs to be organized so that data points like manufacturer, device, model are not duplicated hundreds of times.
Btw, here's a JSFiddle to play with:
https://jsfiddle.net/xpancom/Lq7duahv/
Ideally, the JSON format would be the following:
[
{
"manufacturers": [
{
"manufacturer": "Samsung",
"devices": [
{
"device": "Galaxy A32 5G",
"models": [
{
"model": "SM-A326B",
"data": [
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2022-01-01",
"fw_id": "A326BXXS4AVA1",
"android": "R(Android 11)",
"known_passcode": false,
"afu": false,
"bfu": false,
"bruteforce": false
},
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2021-09-01",
"fw_id": "A326BXXU3AUH7",
"android": "R(Android 11)",
"known_passcode": true,
"afu": false,
"bfu": true,
"bruteforce": true
}
]
},
{
"model": "SM-A326U1",
"data": [
{
"chipset": "Mediatek MT6853V/NZA",
"date": "2021-09-01",
"fw_id": "A326U1UEU5AUJ2",
"android": "R(Android 11)",
"known_passcode": true,
"afu": false,
"bfu": true,
"bruteforce": true
}
]
}
]
}
]
},
{
"manufacturer": "LGE",
"devices": [
{
"device": "LG K31",
"models": [
{
"model": "SM-A326B",
"data": [
{
"chipset": "Mediatek MT6762",
"date": "unknown",
"fw_id": "L355DL10l",
"android": "unknown",
"known_passcode": false,
"afu": false,
"bfu": false,
"bruteforce": false
}
]
}
]
}
]
}
]
}
]
Working in React, here's what I've got so far in trying to massage this data:
const source = data;
const destination = [];
const classifiedTokens = []; // will be used to stored already classified tokens
const classifiedTokensModel = []; // will be used to stored already classified tokens for models
const getNextTokenArray = (source) => {
let unusedToken = null;
const nextTokenArray = source.filter(function (element) {
if (!unusedToken && !classifiedTokens.includes(element['device'])) {
unusedToken = element['device'];
classifiedTokens.push(unusedToken);
}
return unusedToken ? unusedToken === element['device'] : false;
});
return unusedToken ? nextTokenArray : null;
};
// Pass in arrays deconstructed from addToDestination to process third tier nested objects for models
const getNextTokenArrayModel = (tokenObject) => {
let tokenObjectDevice = tokenObject['device'];
let tokenObjectData = tokenObject['data'];
let unusedTokenModel = null;
const nextTokenArrayModel = tokenObjectData.filter(function (element) {
if (!unusedTokenModel && !classifiedTokensModel.includes(element['model'])) {
unusedTokenModel = element['model'];
classifiedTokensModel.push(unusedTokenModel);
}
return unusedTokenModel ? unusedTokenModel === element['model'] : false;
});
//return unusedTokenModel ? nextTokenArrayModel : null;
if (unusedTokenModel) {
if (nextTokenArrayModel.length === 0) return;
let res = {
device: tokenObjectDevice,
model: nextTokenArrayModel[0]['model'],
data: [],
};
nextTokenArrayModel.forEach((element) => {
res.data.push({
manufacturer: element.manufacturer,
chipset: element.chipset,
date: element.date,
fw_id: element.fw_id,
android: element.android,
knownPasscode: element.knownPasscode,
afu: element.afu,
bfu: element.bfu,
bruteforce: element.bruteforce,
});
});
destination.push(res);
} else {
return null;
}
};
const addToDestination = (tokenArray) => {
if (tokenArray.length === 0) return;
let res = {
device: tokenArray[0]['device'],
data: [],
};
tokenArray.forEach((element) => {
res.data.push({
manufacturer: element.manufacturer,
model: element.model,
chipset: element.chipset,
date: element.date,
fw_id: element.fw_id,
android: element.android,
knownPasscode: element.knownPasscode,
afu: element.afu,
bfu: element.bfu,
bruteforce: element.bruteforce,
});
});
getNextTokenArrayModel(res); // Call this to process and group nested model duplicates by device
//destination.push(res);
};
let nextTokenArray = getNextTokenArray(source);
while (nextTokenArray) {
addToDestination(nextTokenArray);
nextTokenArray = getNextTokenArray(source);
}
setTimeout(() => {
document.getElementById('root').innerHTML =
'<pre>' + JSON.stringify(destination, null, 2) + '</pre>';
}, 1000);
};
And here's the JSFiddle again:
https://jsfiddle.net/xpancom/Lq7duahv/
Who can smash this data formatting dilemma?
This answer is not React specific, but one approach would be to use array.reduce() to transform each level/node of the structure as shown in the code snippet below.
const source = [
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326B',
chipset: 'Mediatek MT6853V/NZA',
date: '2022-01-01',
fw_id: 'A326BXXS4AVA1',
android: 'R(Android 11)',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326B',
chipset: 'Mediatek MT6853V/NZA',
date: '2022-01-01',
fw_id: 'A326BXXS4AVA1',
android: 'R(Android 11)',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326U',
chipset: 'Mediatek MT6853V/NZA',
date: '2021-03-01',
fw_id: 'A326USQU1AUD4',
android: 'R(Android 11)',
known_passcode: true,
afu: false,
bfu: true,
bruteforce: true,
},
{
manufacturer: 'Samsung',
device: 'Galaxy A32 5G',
model: 'SM-A326U1',
chipset: 'Mediatek MT6853V/NZA',
date: '2021-09-01',
fw_id: 'A326U1UEU5AUJ2',
android: 'R(Android 11)',
known_passcode: true,
afu: false,
bfu: true,
bruteforce: true,
},
{
manufacturer: 'LGE',
device: 'LG K31',
model: 'LGL355DL',
chipset: 'Mediatek MT6762',
date: 'unknown',
fw_id: 'L355DL10l',
android: 'unknown',
known_passcode: false,
afu: false,
bfu: false,
bruteforce: false,
},
];
function generateTree(data, key) {
return data.reduce((acc, val) => {
// Split the key name from the child data
const { [key.name]: keyName, ...childData } = val;
// Find a tree item in the structure being generated
const treeItem = acc.find((item) => item[key.name] === keyName);
if (treeItem) {
// If found, append child data
treeItem[key.child].push(childData);
} else {
// If not found, create new key and append child data
acc.push({ [key.name]: keyName, [key.child]: [childData] });
}
return acc;
}, []);
}
// Generate manufacturer/device structure
const manufacturers = generateTree(source, {
name: 'manufacturer', // Key name to use as grouping identifier
child: 'devices', // Key name for child data
});
// Generate device/model structure
manufacturers.forEach((manufacturer) => {
manufacturer.devices = generateTree(manufacturer.devices, {
name: 'device',
child: 'models',
});
// Generate model/data structure
manufacturer.devices.forEach((device) => {
device.models = generateTree(device.models, {
name: 'model',
child: 'data',
});
});
});
const destination = [{ manufacturers }];
console.log(destination);

Recursive replace in hierarchy

I have a JSON tree likes this Github link
As you can see, this tree uses formula property on each item to calculate its value. So I need to calculate the value of a few nodes or root nodes.
You can see PL6A, PL6B is the end of tree with type = 1. So I need to replace up to the tree so which formula construct by PLVALUE must be replaced with ACVALUE.
Example:
PL10 formula must be replace with (ACVALUE(PL6A)+ACVALUE(PL6B))+...
How can I do that, thank you and sorry for bad English
Update
I have tried this code:
for (let i = 0; i < accounts.length; i++) {
const item = accounts[i];
let { formula } = accounts[i];
if (item.children.length && item.type === 2) {
//Replace formula with full children formula
item.children.forEach((child) => {
if (formula.indexOf(child.code) > -1) {
const fullInfoChild = accounts.find(
(fullInfoItem) => _.trim(fullInfoItem._id) === _.trim(child._id)
);
formula = _.replace(
formula,
`PLVALUE(${child.code})`,
`(${fullInfoChild.formula})`
);
}
});
} else {
formula = item.formula;
}
accounts[i].formula = formula;
}
and the result, some node work well
{
"_id": "5cf6159f386f5942aabca347",
"code": "PL10",
"formula": "(ACVALUE(PL6A)+ACVALUE(PL6B))+(ACVALUE(PL9))+(ACVALUE(PL7B)+ACVALUE(PL7A))+(ACVALUE(PL8A)+ACVALUE(PL8B))+(ACVALUE(DSRGP))",
"status": 1,
"type": 2,
"parents": [
{
"_id": "5cf61756386f5942aabca365",
"code": "PL28",
"formula": "PLVALUE(PL10)+PLVALUE(PL15)",
"status": 1,
"type": 2
}
],
"children": [
{
"_id": "5cf614f4386f5942aabca342",
"code": "PL6",
"formula": "ACVALUE(PL6A)+ACVALUE(PL6B)",
"status": 1,
"type": 2
},
{
"_id": "5cf6156a386f5942aabca346",
"code": "PL_DSRGP",
"formula": "ACVALUE(DSRGP)",
"status": 1,
"type": 2
},
{
"_id": "5cf6152f386f5942aabca345",
"code": "PL9",
"formula": "ACVALUE(PL9)",
"status": 1,
"type": 2
},
{
"_id": "5cf6150e386f5942aabca343",
"code": "PL7",
"formula": "ACVALUE(PL7B)+ACVALUE(PL7A)",
"status": 1,
"type": 2
},
{
"_id": "5cf6151e386f5942aabca344",
"code": "PL8",
"formula": "ACVALUE(PL8A)+ACVALUE(PL8B)",
"status": 1,
"type": 2
}
],
"totalCurrentYear": 0
},
but if we up some level it not work like this one
{
"_id": "5cf8c78b4aafe73cb56ce424",
"code": "PL30",
"formula": "(((ACVALUE(PL6A)+ACVALUE(PL6B))+(ACVALUE(PL9))+(ACVALUE(PL7B)+ACVALUE(PL7A))+(ACVALUE(PL8A)+ACVALUE(PL8B))+(ACVALUE(DSRGP)))+((ACVALUE(511000)+ACVALUE(735013)+ACVALUE(511100))+(ACVALUE(511002)+ACVALUE(511101)+ACVALUE(554020)+ACVALUE(735015))+(ACVALUE(511003)+ACVALUE(511102)+ACVALUE(735016))+(ACVALUE(511004)+ACVALUE(554010)+ACVALUE(554021)+ACVALUE(735017)+ACVALUE(554030))))+(PLVALUE(PL23)+PLVALUE(PL24)+PLVALUE(PL25))",
"status": 1,
"type": 2,
"children": [
{
"_id": "5cf61756386f5942aabca365",
"code": "PL28",
"formula": "PLVALUE(PL10)+PLVALUE(PL15)",
"status": 1,
"type": 2
},
{
"_id": "5cf8c7bd4aafe73cb56ce426",
"code": "PL26",
"formula": "PLVALUE(PL23)+PLVALUE(PL24)+PLVALUE(PL25)",
"status": 1,
"type": 2
}
],
},
Updated: Add full json data to github link
You could collect the references to the code/formula and the formulas to replace and iterate the formulas to get the values.
var data = [{ _id: "5cf8c78b4aafe73cb56ce424", code: "PL30", formula: "PLVALUE(PL28)+PLVALUE(PL26)", status: 1, type: 2, children: [{ _id: "5cf61756386f5942aabca365", code: "PL28", formula: "PLVALUE(PL10)+PLVALUE(PL15)", status: 1, type: 2 }, { _id: "5cf8c7bd4aafe73cb56ce426", code: "PL26", formula: "PLVALUE(PL23)+PLVALUE(PL24)+PLVALUE(PL25)", status: 1, type: 2 }] }, { _id: "5cf61756386f5942aabca365", code: "PL28", formula: "PLVALUE(PL10)+PLVALUE(PL15)", status: 1, type: 2, children: [{ _id: "5cf6159f386f5942aabca347", code: "PL10", formula: "PLVALUE(PL6)+PLVALUE(PL9)+PLVALUE(PL7)+PLVALUE(PL8)+PLVALUE(PL_DSRGP)", status: 1, type: 2 }, { _id: "5cf61741386f5942aabca364", code: "PL15", formula: "PLVALUE(PL11)+PLVALUE(PL12)+PLVALUE(PL13)+PLVALUE(PL14)", status: 1, type: 2 }], totalCurrentYear: 0 }, { _id: "5cf6159f386f5942aabca347", code: "PL10", formula: "PLVALUE(PL6)+PLVALUE(PL9)+PLVALUE(PL7)+PLVALUE(PL8)+PLVALUE(PL_DSRGP)", status: 1, type: 2, children: [{ _id: "5cf614f4386f5942aabca342", code: "PL6", formula: "ACVALUE(PL6A)+ACVALUE(PL6B)", status: 1, type: 2 }, { _id: "5cf6156a386f5942aabca346", code: "PL_DSRGP", formula: "ACVALUE(DSRGP)", status: 1, type: 2 }, { _id: "5cf6152f386f5942aabca345", code: "PL9", formula: "ACVALUE(PL9)", status: 1, type: 2 }, { _id: "5cf6150e386f5942aabca343", code: "PL7", formula: "ACVALUE(PL7B)+ACVALUE(PL7A)", status: 1, type: 2 }, { _id: "5cf6151e386f5942aabca344", code: "PL8", formula: "ACVALUE(PL8A)+ACVALUE(PL8B)", status: 1, type: 2 }] }, { _id: "5cf614f4386f5942aabca342", code: "PL6", formula: "ACVALUE(PL6A)+ACVALUE(PL6B)", status: 1, type: 2, children: [{ _id: "5cf61869386f5942aabca368", code: "PL6A", type: 1, status: 1 }, { _id: "5cf6187f386f5942aabca36a", code: "PL6B", type: 1, status: 1 }], totalCurrentYear: 0 }],
replace = (formula, values) => formula.replace(/PLVALUE\(([^\)]+)\)/g, (full, group) => group in values
? `(${values[group].object[values[group].key]})`
: full
),
getNestedValues = (r, object) => {
r.values[object.code] = { object, key: object.type === 1 ? 'code' : 'formula' };
if (object.formula?.includes('PLVALUE')) r.formulas.push(object);
if (object.children) object.children.reduce(getNestedValues, r);
return r;
},
{ values, formulas } = data.reduce(getNestedValues, { values: {}, formulas: [] });
// while (formulas.length) { // only if all targets are known
let i = formulas.length;
while (i--) {
let s = replace(formulas[i].formula, values);
if (s === formulas[i].formula) continue;
formulas[i].formula = s;
formulas.splice(i, 1);
}
// } // only if all targets are known
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

FindAndUpdate How to check if document was really updated

Imagine the following model:
var Office =
{
id: 1,
name: "My Office",
branches:
[
{
adddress: "Some street, that avenue",
isPrincipal: true,
},
{
adddress: "Another address",
isPrincipal: false,
},
]
}
I'd like to remove a branch, but we can't let the user remove the principal branch from an office. So here's my function:
remove: function(body)
{
return new Promise(function(resolve, reject)
{
return Office.findByIdAndUpdate(1, { $pull: {'branches': {_id: body.branch.id}}}, { new: true })
.then(function(updatedOffice){
resolve(updatedOffice)
})
.catch(function(error){
reject(error);
});
})
}
I have some doubts here:
As you can see I haven't included the another WHERE on the isPrincipal property, this is because I don't know how can I determine whether the office object got actually changed. Because the object will alway be retrieved but... How can I know this for sure?
Is FindByIdAndUpdate the best approach considering we can't let a user delete the principal branch, and if he's trying to do so, we have to show a warning.
The only real reliable way to see if an update was applied for something like a $pull is to basically check the returned document and see if the data you intended to $pull is still in there or not.
That's for any of the "findAndUpdate" variety of actions, and there is a valid reason for that as well as it also being the case that a plain .update() will actually "reliably" tell you if the modification was in fact made.
To walk through the cases:
Check the Returned Content
This basically involves looking at the array in the returned document in order to see if what we asked to remove is actually there:
var pullId = "5961de06ea264532c684611a";
Office.findByIdAndUpdate(1,
{ "$pull": { "branches": { "_id": pullId } } },
{ "new": true }
).then(office => {
// Check if the supplied value is still in the array
console.log(
"Still there?: %s",
(office.branches.find( b => b._id.toHexString() === pullId))
? true : false
);
}).catch(err => console.error(err))
We use .toHexString() in order to compare the actual value from an ObjectId since JavaScript just does not do "equality" with "Objects". You would check on both "left" and "right" if supplying something that was already "cast" to an ObjectId value, but in this case we know the other input is a "string".
Just use .update(), "It's reliable"
The other case here to consider brings into question if you "really need" the returned modified data anyway. Because the .update() method, will reliably return a result telling you if anything was actually modified:
Office.update(
{ "_id": 1 },
{ "$pull": { "branches": { "_id": pullId } } },
).then(result => {
log(result);
}).catch(err => console.error(err))
Where result here will look like:
{
"n": 1,
"nModified": 1, // <--- This always tells the truth, and cannot lie!
"opTime": {
"ts": "6440673063762657282",
"t": 4
},
"electionId": "7fffffff0000000000000004",
"ok": 1
}
And in which the nModified is a "true" indicator of whether something "actually updated". Therefore if it's 1 then the $pull actually had an effect, but when 0 nothing was actually removed from the array and nothing was modified.
This is because the method actually uses the updated API, which does have reliable results indicating actual modifications. The same would apply to something like a $set which did not actually change the value because the the value supplied was equal to what already existed in the document.
findAndModify Lies!
The other case here you might think of when looking closely at the documentation is to actually inspect the "raw result" and see if the document was modified or not. There is actually an indicator in the specification for this.
The problem is ( as well as requiring more work with Promises ) that the result is not actually truthful:
var bogusId = "5961de06ea264532c684611a"; // We know this is not there!
Promise((resolve,reject) => {
Office.findByIdAndUpdate(1,
{ "$pull": { "branches": { "_id": bogusId } } },
{ "new": true, "passRawResult" },
(err,result,raw) => { // We cannot pass multiple results to a Promise
if (err) reject(err);
resolve({ result, raw }); // So we wrap it!
}
)
})
.then(response => log(response.raw))
.catch(err => console.error(err));
The problem here is that even when we "know" this should not modify, the response says otherwise:
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1 // <--- LIES! IT'S ALL LIES!!!
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961de06ea264532c6846118"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
So even after all that work to get the "third" argument out of the callback response, we still did not get told the correct information about the update.
Concluding
So if you want to "reliably" do this with a single request ( and you cannot reliably do that with multiple requests since the document could change in between! ) then your two options are:
Check the returned document to see if the data you wanted to remove is still there.
Forget returning a document and trust that .update() always tells you the "truth" ;)
Which one of these you use depends on the application usage pattern, but those are the two different ways of returning an "reliable" result.
Bit of a Listing
So just to be sure, here's a listing that goes through all the examples and demonstrates what they actually return:
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/test');
const branchesSchema = new Schema({
address: String,
isPrincipal: Boolean
});
const officeSchema = new Schema({
_id: Number,
name: String,
branches: [branchesSchema]
},{ _id: false });
const Office = mongoose.model('Office', officeSchema);
function log(data) {
console.log(JSON.stringify(data,undefined,2))
}
const testId = "5961a56d3ffd3d5e19c61610";
async.series(
[
// Clean data
(callback) =>
async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Insert some data and pull
(callback) =>
async.waterfall(
[
// Create and demonstrate
(callback) =>
Office.create({
_id: 1,
name: "My Office",
branches: [
{
address: "Some street, that avenue",
isPrincipal: true
},
{
address: "Another address",
isPrincipal: false
},
{
address: "Third address",
isPrincipal: false
}
]
},callback),
// Demo Alternates
(office,callback) =>
async.mapSeries(
[true,false].map((t,i) => ({ t, branch: office.branches[i] })),
(test,callback) =>
(test.t)
? Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": test.branch._id } } },
{ "new": true , "passRawResult": true },
(err,result,raw) => {
if (err) callback(err);
log(result);
log(raw);
callback();
})
: Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": test.branch._id } } },
{ "new": true } // false here
).then(result => {
log(result);
console.log(
"Present %s",
(result.branches.find( b =>
b._id.toHexString() === test.branch._id.toHexString() ))
? true : false
);
callback();
}).catch(err => callback(err)),
callback
)
],
callback
),
// Find and demonstate fails
(callback) =>
async.waterfall(
[
(callback) => Office.findOne({},callback),
(office,callback) =>
async.eachSeries([true,false],(item,callback) =>
(item)
? Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": testId } } },
{ "new": true, "passRawResult": true },
(err,result,raw) => {
if (err) callback(err);
log(result);
log(raw);
callback();
}
)
: Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": testId } } },
{ "new": true }
).then(result => {
console.log(result);
console.log(
"Present %s",
(result.branches.find( b =>
b._id.toHexString() === office.branches[0]._id.toHexString()))
? true : false
);
callback();
})
.catch(err => callback(err)),
callback)
],
callback
),
// Demonstrate update() modified shows 0
(callback) =>
Office.update(
{},
{ "$pull": { "branches": { "_id": testId } } }
).then(result => {
log(result);
callback();
})
.catch(err => callback(err)),
// Demonstrate wrapped promise
(callback) =>
Office.findOne()
.then(office => {
return new Promise((resolve,reject) => {
Office.findByIdAndUpdate(office._id,
{ "$pull": { "branches": { "_id": testId } } },
{ "new": true, "passRawResult": true },
(err,result,raw) => {
if (err) reject(err);
resolve(raw)
}
);
})
})
.then(office => {
log(office);
callback();
})
.catch(err => callback(err))
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
);
And the output it produces:
Mongoose: offices.remove({}, {})
Mongoose: offices.insert({ _id: 1, name: 'My Office', branches: [ { address: 'Some street, that avenue', isPrincipal: true, _id: ObjectId("5961e5211a73e8331b44d74b") }, { address: 'Another address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d74a") }, { address: 'Third address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d749") } ], __v: 0 })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74b") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
"_id": 1,
"name": "My Office",
"__v": 0,
"branches": [
{
"address": "Another address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d74a"
},
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
]
}
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Another address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d74a"
},
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74a") } } }, { new: true, upsert: false, remove: false, fields: {} })
{
"_id": 1,
"name": "My Office",
"__v": 0,
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
]
}
Present false
Mongoose: offices.findOne({}, { fields: {} })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
"_id": 1,
"name": "My Office",
"__v": 0,
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
]
}
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, upsert: false, remove: false, fields: {} })
{ _id: 1,
name: 'My Office',
__v: 0,
branches:
[ { address: 'Third address',
isPrincipal: false,
_id: 5961e5211a73e8331b44d749 } ] }
Present true
Mongoose: offices.update({}, { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, {})
{
"n": 1,
"nModified": 0,
"opTime": {
"ts": "6440680872013201413",
"t": 4
},
"electionId": "7fffffff0000000000000004",
"ok": 1
}
Mongoose: offices.findOne({}, { fields: {} })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": 1,
"name": "My Office",
"branches": [
{
"address": "Third address",
"isPrincipal": false,
"_id": "5961e5211a73e8331b44d749"
}
],
"__v": 0
},
"ok": 1,
"_kareemIgnore": true
}
Finding and updating in two steps would better in this case, as you just said you'll have the option of warning the user.
A note on find. You have an array of objects branches. To match more than one field in find $elemMatch is needed. The query will look something like:
Office.findOne({_id: 1, "branches" : {$elemMatch: {"_id": body.branch.id, "isPrincipal": false}}})
Which will either return the office document or not. If it does, you proceed with findByIdAndUpdate (which is better than modifying and saving the already found document). If it does not, return a forbidden message to the user.

How can I sort array by a field inside a MongoDB document

I have document called question
var QuestionSchema = new Schema({
title: {
type: String,
default: '',
trim: true
},
body: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
category: [],
comments: [{
body: {
type: String,
default: ''
},
root: {
type: String,
default: ''
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now
}
}],
tags: {
type: [],
get: getTags,
set: setTags
},
image: {
cdnUri: String,
files: []
},
createdAt: {
type: Date,
default: Date.now
}
});
As a result, I need to sort comments by root field, like this
I tried to sort the array of comments manually at backend and tried to use aggregation, but I was not able to sort this. Help please.
Presuming that Question is a model object in your code and that of course you want to sort your "comments by "date" from createdAt then using .aggregate() you would use this:
Question.aggregate([
// Ideally match the document you want
{ "$match": { "_id": docId } },
// Unwind the array contents
{ "$unwind": "comments" },
// Then sort on the array contents per document
{ "$sort": { "_id": 1, "comments.createdAt": 1 } },
// Then group back the structure
{ "$group": {
"_id": "$_id",
"title": { "$first": "$title" },
"body": { "$first": "$body" },
"user": { "$first": "$user" },
"comments": { "$push": "$comments" },
"tags": { "$first": "$tags" },
"image": { "$first": "$image" },
"createdAt": { "$first": "$createdAt" }
}}
],
function(err,results) {
// do something with sorted results
});
But that is really overkill since you are not "aggregating" between documents. Just use the JavaScript methods instead. Such as .sort():
Quesion.findOneById(docId,function(err,doc) {
if (err) throw (err);
var mydoc = doc.toObject();
mydoc.questions = mydoc.questions.sort(function(a,b) {
return a.createdAt > b.createdAt;
});
console.log( JSON.stringify( doc, undefined, 2 ) ); // Intented nicely
});
So whilst MongoDB does have the "tools" to do this on the server, it makes the most sense to do this in client code when you retrieve the data unless you actually need to "aggregate" accross documents.
But both example usages have been given now.

Categories