How can I rename or map multiple keys to a new key name? - node.js

I am working with JSON data:
[
{
"name": "Person1",
"a_miles": "110 mi"
},
{
"name": "Person2",
"b_miles": "22 mi"
},
{
"name": "Person3",
"a_miles": "552 mi"
}
]
I need to rename a_miles or b_miles to "total" , but can't seem to get .map() to work since it won't allow multiple keys to 1 final key. Each item with have either A or B as well.
This is what I tried so far:
.data(function(data) {
console.log('checking for KMs');
for(key in data) {
console.log(key);
if(data[key].includes(' km')){
console.log('KM found, deleting %s', key)
delete data[key];
}
}
//console.log(data)
savedData.push(data);
})
.done(function() {
var formattedJson = savedData.map(({
name,
a_miles:total,
b_miles:total
}) => ({
name,
total
}));
Maybe I'm over complicating things, I just need to have a total key/value that replaces a or b so it's consistent through the whole array result.

Actually you can apply a .map to an object, as already answered here.
Here is an example:
a.map(obj => {
Object.keys(obj).map( key => {
if (key.match('_miles')){
obj['total'] = obj[key];
delete obj[key];
}
})
})
console.log(a);
// [ { name: 'Person1', total: '110 mi' },
// { name: 'Person2', total: '22 mi' },
// { name: 'Person3', total: '552 mi' } ]
Where a is the array you proposed. Hope it will help

Looking at your destructuring, If you JUST want a_miles or b_miles you can use your syntax also just add an OR between them:
.done(function () {
const formattedJson = savedData.map(({
name,
a_miles,
b_miles
}) => ({
name,
total: a_miles || b_miles
}))
});

Related

CouchDB View, list key with duplicate count

In CouchDB I have a collection of articles. Each article has a tags property.
I wrote this map function to list all tags in database
function (doc) {
for(var i = 0; i < doc.metaKeywords.length; i++)
emit(doc.metaKeywords[i], 1)
}
But when it list all tags, it show duplicates of tags. I want to show only one time for each tag and show duplicate number of each tags instead of emit duplicate rows of same key.
What should I do to modify this map function?
The map function is OK, but there is no reason to emit the value 1.
Regardless, the simple builtin reduce function _count and the right query does everything required.
Working with CouchDB demands understanding the B-tree and it's documentation has a great rundown of it in its Reduce/Rereduce documentation. I highly recommend grok'ing that information.
The snippet below highlights its usage via pouchdb. The design document specifies both map and reduce, e.g.
{
"_id": "_design/SO-72078037",
"views": {
"tags": {
"map": `function (doc) {
if(doc.tags) doc.tags.forEach(tag => emit(tag));
}`,
"reduce": "_count",
}
}
}
Straight forward stuff. To get key (tag) counts the query is as simple:
{
reduce: true,
include_docs: false,
group_level: 1
}
Again, the CouchDB documentation is great - read up on group level queries
const gel = id => document.getElementById(id);
async function showReduceDocs(view) {
let result = await db.query(view, {
reduce: true,
include_docs: false,
group_level: 1
});
// show
gel('view_reduce').innerText = result.rows.map(row => JSON.stringify(row))
.join('\n');
}
async function showViewDocs(view) {
let result = await db.query(view, {
reduce: false,
include_docs: false
});
gel('view_docs').innerText = result.rows.map(row => JSON.stringify(row))
.join('\n');
}
function getDocsToInstall() {
return [{
tags: ["A", "B", "C"]
},
{
tags: ["A", "B"]
},
{
tags: ["A"]
},
{
// design document
"_id": "_design/SO-72078037",
"views": {
"tags": {
"map": `function (doc) {
if(doc.tags) doc.tags.forEach(tag => emit(tag));
}`,
"reduce": "_count",
}
}
},
];
}
const db = new PouchDB('SO-72078037', {
adapter: 'memory'
});
(async() => {
// install docs and show view in various forms.
await db.bulkDocs(getDocsToInstall());
showReduceDocs('SO-72078037/tags');
showViewDocs('SO-72078037/tags');
})();
<script src="https://cdn.jsdelivr.net/npm/pouchdb#7.1.1/dist/pouchdb.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<div>View: Reduce</div>
<pre id='view_reduce'></pre>
<hr/>
<div>View</div>
<pre id='view_docs'></pre>

Mongoose - how to fetch specific number and message ID

I'm trying to make the bot basically edit the message of any specific case mentioned for example if i do -case 5 test it will look for case 5 and it's message. So far when i do it, it basically changes the recent case number message, instead of the one i want it to change. like if i do case 5 test and the latest case is #9, it will change 9 instead of 5.
This is how i send the message:
Modlog.findOneAndUpdate({ guildID: msg.channel.guild.id }, { $inc: { 'caseID': 1 } }, { new: true }, async function (err, doc) {
if (err) throw err;
if (!doc) return;
if (doc.modLog.enabled) {
if (msg.channel.guild.channels.get(doc.modLog.channelID)) {
let m = await msg.channel.guild.channels.get(doc.modLog.channelID).createMessage({
embed: {
title: `${action} | Case #${doc.caseID}`,
color: colour,
fields: [
{
name: 'User',
value: user,
inline: true
},
{
name: 'Moderator',
value: moderator ? moderator : 'No issuer.',
inline: true
},
{
name: 'Reason',
value: reason ? reason : 'No reason.'
}
]
}
});
doc.messageID = m.id;
doc.type = action;
doc.caseID = doc.caseID;
//doc.caseID = m.id
doc.moderatorID = moderator,
doc.targetID = user
doc.save();
}
}
})
that is how i send my message. And you can see i'm storing the things so when someone changes a specific case's reason, for example: case 5 spamming, i would want it to look for caseID 5, and then edit the message through it's ID. but i'm not sure how am i doing it wrong. I'm trying to make each case store it's own message ID and i would really appreciate any help. This is what i use to look for the case and edit's reason.
Modlog.findOne({ guildID: msg.guildID }, async (err, doc) => {
if (err) throw err;
if (!doc.modLog.enabled) return msg.channel.createMessage(`Modlog is not enabled in this server! ${this.emoji.cross}`);
if (isNaN(Number(caseID))) return msg.channel.createMessage(`Case \`#${caseID}\` was not a number! ${this.emoji.cross}`);
if (doc.caseID === undefined) return msg.channel.createMessage(`Couldn\'t find case \`#${caseID}\`! ${this.emoji.cross}`);
const moderator = this.bot.users.get(doc.moderatorID) || {
username: 'Unknown User',
discriminator: '0000'
}
const target = this.bot.users.get(doc.targetID) || {
username: 'Unknown User',
discriminator: '0000'
}
let embed = {
title: `${doc.type} | Case #${doc.caseID}`,
fields: [
{
name: 'User',
value: `${target.username}#${target.discriminator} (${target.id})`,
inline: true
},
{
name: 'Moderator',
value: `${moderator.username}#${moderator.discriminator} (${moderator.id})`,
inline: true
},
{
name: 'Reason',
value: reason
}
]
};
try {
await this.bot.editMessage(doc.modLog.channelID, doc.messageID, { embed: embed });
await msg.channel.createMessage(`Case **#${caseID}** has been updated. ${this.emoji.tick}`);
} catch (e) {
await msg.channel.createMessage(`I\'m unable to edit that case or it has been deleted. ${this.emoji.cross}`);
}
});```
Solution: Search for Case ID
It seems you didn't look for the case ID, and only looked for the guild's ID in the filter parameter.
Modlog.findOneAndUpdate({ guildID: msg.channel.guild.id }, { ... }, { ... }, ... {
...
}
In your code, only guildID was passed into the filter parameter. This causes Mongoose to look for the most recently initialized document for the server. For your case, you should also pass caseID into the filter parameter.
Modlog.findOneAndUpdate({ guildID: msg.channel.guild.id, caseID: caseIDArg }, { ... }, { ... }, ... {
...
}
Replace caseIDArg with your supposed caseID argument in the message's content. For example, args[1] or however you programmed your argument handler to work.
Hope this helped to answer your question!

MongoDB - find one and add a new property

Background: Im developing an app that shows analytics for inventory management.
It gets an office EXCEL file uploaded, and as the file uploads the app convert it to an array of JSONs. Then, it comapers each json object with the objects in the DB, change its quantity according to the XLS file, and add a timestamp to the stamps array which contain the changes in qunatity.
For example:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":100,
"Warehouse":4,
"stamps":[]
}
after the XLS upload, lets say we sold 10 units, it should look like that:
{"_id":"5c3f531baf4fe3182cf4f1f2",
"sku":123456,
"product_name":"Example",
"product_cost":10,
"product_price":60,
"product_quantity":90,
"Warehouse":4,
"stamps":[{"1548147562": -10}]
}
Right now i cant find the right commands for mongoDB to do it, Im developing in Node.js and Angular, Would love to read some ideas.
for (let i = 0; i < products.length; i++) {
ProductsDatabase.findOneAndUpdate(
{"_id": products[i]['id']},
//CHANGE QUANTITY AND ADD A STAMP
...
}
You would need two operations here. The first will be to get an array of documents from the db that match the ones in the JSON array. From the list you compare the 'product_quantity' keys and if there is a change, create a new array of objects with the product id and change in quantity.
The second operation will be an update which uses this new array with the change in quantity for each matching product.
Armed with this new array of updated product properties, it would be ideal to use a bulk update for this as looping through the list and sending
each update request to the server can be computationally costly.
Consider using the bulkWrite method which is on the model. This accepts an array of write operations and executes each of them of which a typical update operation
for your use case would have the following structure
{ updateOne :
{
"filter" : <document>,
"update" : <document>,
"upsert" : <boolean>,
"collation": <document>,
"arrayFilters": [ <filterdocument1>, ... ]
}
}
So your operations would follow this pattern:
(async () => {
let bulkOperations = []
const ids = products.map(({ id }) => id)
const matchedProducts = await ProductDatabase.find({
'_id': { '$in': ids }
}).lean().exec()
for(let product in products) {
const [matchedProduct, ...rest] = matchedProducts.filter(p => p._id === product.id)
const { _id, product_quantity } = matchedProduct
const changeInQuantity = product.product_quantity - product_quantity
if (changeInQuantity !== 0) {
const stamps = { [(new Date()).getTime()] : changeInQuantity }
bulkOperations.push({
'updateOne': {
'filter': { _id },
'update': {
'$inc': { 'product_quantity': changeInQuantity },
'$push': { stamps }
}
}
})
}
}
const bulkResult = await ProductDatabase.bulkWrite(bulkOperations)
console.log(bulkResult)
})()
You can use mongoose's findOneAndUpdate to update the existing value of a document.
"use strict";
const ids = products.map(x => x._id);
let operations = products.map(xlProductData => {
return ProductsDatabase.find({
_id: {
$in: ids
}
}).then(products => {
return products.map(productData => {
return ProductsDatabase.findOneAndUpdate({
_id: xlProductData.id // or product._id
}, {
sku: xlProductData.sku,
product_name: xlProductData.product_name,
product_cost: xlProductData.product_cost,
product_price: xlProductData.product_price,
Warehouse: xlProductData.Warehouse,
product_quantity: productData.product_quantity - xlProductData.product_quantity,
$push: {
stamps: {
[new Date().getTime()]: -1 * xlProductData.product_quantity
}
},
updated_at: new Date()
}, {
upsert: false,
returnNewDocument: true
});
});
});
});
Promise.all(operations).then(() => {
console.log('All good');
}).catch(err => {
console.log('err ', err);
});

How can I group items close to each other without knowing the distribution?

I'm preparing some data about sold apartment prices for regression analysis. One category is what street the houses are on, but some streets have very different areas, so I want to make a category with the combination of construction year and street name.
Broadway 1910
Broadway 2001
Forexample my challenge is that sometimes the construction spans over several two years. The data is from Sweden, known for huge centralized housing projects. I would like to group these houses together into a period somehow. This is my current code. I know it's not very efficient, but it will only run once on a not huge dataset.
(async () =>{
let client;
try {
client = await MongoClient;
let collection = client.db("booliscraper").collection("sold");
let docs = await collection.find();
await docs.forEach((sale) => {
sale.street = sale.location.address.streetAddress.split(/[0-9]/)[0] + sale.location.namedAreas[0]
sale.streetYear = sale.street+" "+sale.constructionYear
log(sale);
collection.replaceOne({_id: ObjectId(sale._id)}, doc)
});
client.close();
} catch(err) {
log(err)
}
})()
As you correctly said, your current code is inefficient when it comes to dealing with huge datasets so instead of making several calls to the server to do replaceOne within your forEach loop, you can create an aggregate query that computes the category fields you want with the $group pipeline and push the documents that fall into those categories into an array that you will later use to do a bulk update.
For the bulk update you can use bulkWrite method on the collection that will have multiple updateMany operations.
The following operation shows the intuition above in practice:
(async () => {
try {
let client = await MongoClient;
let collection = client.db("booliscraper").collection("sold");
let pipeline = [
{ '$group': {
'_id': {
'street': {
'$concat': [
{
'$arrayElemAt': [
{ '$split': [
'$location.address.streetAddress',
/[0-9]/
] },
0
]
},
{ '$arrayElemAt': [ '$location.namedAreas', 0 ] },
]
},
'streetYear': { '$concat': ['$street', ' ', '$constructionYear'] }
},
'ids': { '$push': '$_id' }
} }
]
let docs = await collection.aggregate(pipeline);
let ops = docs.map(({ _id, ids }) => ({
'updateMany': {
'filter': { '_id': { '$in': ids } },
'update': { '$set': {
'street': _id.street, 'streetYear': _id.streetYear
} }
}
}));
let result = await collection.bulkWrite(ops);
log(result)
client.close()
} catch(err) {
log(err)
}
})()

how does one update numbers inside mongoose model nested array

can someone please explain what I am doing wrong. I am attempting to update a number value inside a nested array on my mongoose schema by adding two numbers
here is the section in question
$set: {
"shareHolders.$.shares": Number(req.existingStock) + Number(req.stock)
}
req.existing shares is say 100 and req.stock is a formatted as a string but equals say 100 so, in short, the new value for the shares should be 200
BUT when i run the code the shares of the said shareholder does not change it remains the original value.
here is the full snippet
module.exports.updateShareHolder = function(req, callback) {
console.log('updateShareHolder');
console.log(req);
console.log(req.existingStock + Number(req.stock));
Company.update({
"_id": req.companyID,
"shareHolders.userId": req.userID
}, {
$push: {
"shareHolders.$.agreements": {
agreementID: req.agreementID
}
}
}, {
$set: {
"shareHolders.$.shares": Number(req.existingStock) + Number(req.stock)
}
}, function(err) {
if (err) {
console.log(err);
callback(err, err);
} else {
console.log('updateShareHolder');
callback(null, 'success');
}
})
};
Convert to a number before doing your update.
const updatedStock = Number(req.existingStock) + Number(req.stock)
then
$set: {
"shareHolders.$.shares": updatedStock
}

Resources