Node js Json add array - node.js

This is my query
var db = firebase.database();
var ref = db.ref('Users');
ref.on('child_added', function(snapshot){
console.log(snapshot.val())
});
and this is out put
{ callCount: 1,
favoriteCount: 1,
interest: [ 'dogs', 'cats', 'technology' ],
tickCount: 1 }
{ callCount: 15,
favoriteCount: 7,
interest: [ 'Technology', 'Azure', 'Asp.NET MVC', 'JavaScript' ],
tickCount: 3 }
I want to add a array and show it like this
[{ callCount: 1,
favoriteCount: 1,
interest: [ 'dogs', 'cats', 'technology' ],
tickCount: 1 },
{ callCount: 15,
favoriteCount: 7,
interest: [ 'Technology', 'Azure', 'Asp.NET MVC', 'JavaScript' ],
tickCount: 3 }]
How can I do ?

This will not be possible with child_added event, because it never gives any indication about reaching the end of the dataset, and will continue to emit new items when they are added.
Instead, you should subscribe to the value which will emit snapshot of the full tree in the beginning and whenever anything changes.
Since you only need to get the value once, here's how you do it:
const db = firebase.database();
const ref = db.ref('Users');
ref.once('value', snapshot => {
const result = [];
snapshot.forEach(item => {
result.push(item.val());
return false;
});
// Do something with the result
});
A few things to note here:
We are subscribing to event using once since we are only interested in the initial value. If you want to react to changes in dataset somehow, you may use on instead.
We are using forEach to get to the snapshot value, instead of val because we care about the item order. forEach guarantees that the callback will be invoked with all of the snapshot keys in order, as specified by orderBy query. val on the other hand, would return an object with unreliable ordering of the keys.
forEach will stop as soon as the callback returns true, which we don't want to happen. Returning nothing will not cancel enumeration, but it's better to be explicit in case someone decides to be concise and removes the curly braces.

Related

How to update entities in a table from an array?

I'm trying to figure out how to update many elements at once. Suppose I have the following array:
[
{
id: 100,
order: 1,
},
{
id: 101,
order: 2,
},
{
id: 102,
order: 3,
},
]
I then transform this array, replacing the values of order. The resulting array becomes the following:
[
{
id: 102,
order: 1,
},
{
id: 101,
order: 2,
},
{
id: 100,
order: 3,
},
]
I use this on the frontend to render a list in the appropriate order, based on the value of order.
But how can I update these 3 entities in my database?
I can obviously make 3 UPDATE statements:
const promises = [];
newArray.forEach(({ id, order }) => {
promises.push(
// executeMutation is just a custom query builder
executeMutation({
query: `UPDATE my_table SET order = ${order} WHERE id = ${id}'`
})
)
})
await Promise.all(promises)
But is it possible to do this in one query?
You can do this using the UNNEST function. First you'll need to handle the query parameters properly. https://www.atdatabases.org/ does this for you, otherwise you need to separately pass a string with placeholders and then the values. If you use #databases, the code could look like:
await database.query(sql`
UPDATE my_table
SET order = updates_table.order
FROM (
SELECT
UNNEST(${newArray.map(v => v.id)}::INT[]) as id,
UNNEST(${newArray.map(v => v.order)}::INT[]) as order
) AS updates_table
WHERE my_table.id = updates_table.id
`);
The trick here is that UNNEST lets you take an array for each column and turn that into a kind of temporary table. You can then use that table to filter & update the records.

Mongoose bulk update

I want to be able to update an array of objects where each object has a new unique value assigned to it.
Here is a simplified example of what I'm doing. items is an array of my collection items.
let items = [{_id: '903040349304', number: 55}, {_id: '12341244', number: 1166}, {_id: '667554', number: 51115}]
I want to assign a new number to each item, and then update it in collection:
items = items.map(item => {
item.number = randomInt(0, 1000000);
return item;
})
What would be the best way to update the collection at once? I know that I could do it in forEach instead of map, how ever this seems as a dirty way of doing it, as it won't do the bulk update.
items.forEach(async (item) => {
await this.itemModel.update({_id: item._id}, {number: randomInt(0, 1000000)})
});
I've checked the updateMany as well but my understanding of it is that it's only used to update the documents with a same new value - not like in my case, that every document has a new unique value assigned to it.
After a bit of thinking, I came up with this solution using bulkWrite.
const updateQueries = [];
items.forEach(async (item) => {
updateQueries.push({
updateOne: {
filter: { _id: item._id },
update: { number: item.number },
},
});
});
await this.itemModel.bulkWrite(updateQueries);
About bulkWrite
Sends multiple insertOne, updateOne, updateMany, replaceOne,
deleteOne, and/or deleteMany operations to the MongoDB server in one
command. This is faster than sending multiple independent operations
(like) if you use create()) because with bulkWrite() there is only one
round trip to MongoDB.
You can call an aggregate() to instantly update them without needing to pull them first:
Step1: get a random number with mongoDb build in $rand option which returns a number between 0 and 1
Step2: $multiply this number by 1000000 since that is what you defined ;)
Step3: use another $set with $floor to remove the decimal portion
YourModel.aggregate([
{
'$set': {
'value': {
'$multiply': [
{
'$rand': {}
}, 1000000
]
}
}
}, {
'$set': {
'value': {
'$floor': '$value'
}
}
}
])
Here a picture of how that looks in mongo Compass as a proof of it working:

Create a list of functions to be executed from constant file in node.js

I have list of functions to be executed based on the value of a variable, age. These functions correspond to a number of vaccine doses given to an individual.
If childern, then no function (i.e. vaccine) is to be executed.
If youth then function firstDose is to be executed.
If adult then two functions, firstDose and secondDose is to be
executed.
If old then three functions, firstDose, secondDose, boosterDose to be executed
Currently, I build this list up manually, with switch and case statements. If the condition is satisfied, then I push the required functions to an array of functions to execute later, like so for old:
vaccines.push(() => this.firstDose(ageVerification))
vaccines.push(() => this.secondDose(ageVerification, previousVaccineCertificate))
vaccines.push(() => this.boosterDose(ageVerification, previousVaccineCertificate, medicalDocuments))
Instead of embedding this in code, I would like to base it on configuration (currently in a file, may move to a database at a later point in time). The constants in a file should look like this:
export const ageVaccineMapping = {
children: null,
youth: [this.firstDose(ageVerification)],
adult:[this.firstDose(ageVerification), this.secondDose(ageVerification, previousVaccineCertificate)],
old:[this.firstDose(ageVerification), this.secondDose(ageVerification, previousVaccineCertificate), this.boosterDose(ageVerification, previousVaccineCertificate, medicalDocuments)],
}
My Question is, How should I create such a file of constants with varying function argument, how do I import constant file in working ts file, and how do I access that function array in code?
I am using Typescript and promise.all to execute. Any leads will be helpful. Anonymous functions are also acceptable.
Please not that, this is just example, this is similar to my use-case. For old people, I want to execute all 3 functions and not just boosterDose function.
Also, I want the constant list should have varying list of function arguments.
Assuming you have this mapping definition in JSON:
{
"groups": {
"children": [],
"youth": ["firstDose"],
"adult": ["firstDose", "secondDose"],
"old": ["firstDose", "secondDose", "boosterDose"]
},
"arguments": {
"firstDose": ["ageVerification"],
"secondDose": ["ageVerification", "previousVaccineCertificate"],
"boosterDose": ["ageVerification", "previousVaccineCertificate", "medicalDocuments"]
}
}
and your person objects look like this:
const people = [
{group: 'adult', ageVerification: 'A1', previousVaccineCertificate: 'B1', medicalDocuments: null},
{group: 'children', ageVerification: null, previousVaccineCertificate: null, medicalDocuments: null},
{group: 'old', ageVerification: 'A3', previousVaccineCertificate: 'B3', medicalDocuments: 'C3'},
{group: 'youth', ageVerification: 'A4', previousVaccineCertificate: null, medicalDocuments: null},
];
Then you could create verification service that maps the property names from the JSON into functions and arguments:
class VerificationService {
mapping;
constructor(mapping) {
this.mapping = mapping;
}
verifyAsync(person) {
const pendingVerifications = this.mapping.groups[person.group].map(funcName => {
const args = this.mapping.arguments[funcName].map(prop => person[prop]);
return this[funcName](...args);
});
return Promise.all(pendingVerifications).then(results => results.every(r => r));
},
firstDose(age) {
console.log('--> verifying firstDose:', [...arguments].join(', '));
return Promise.resolve(true); // simulate async result
},
secondDose(age, previous) {
console.log('--> verifying secondDose:', [...arguments].join(', '));
return Promise.resolve(true); // simulate async result
},
boosterDose(age, previous, medical) {
console.log('--> verifying boosterDose:', [...arguments].join(', '));
return Promise.resolve(true); // simulate async result
}
}
Called like this:
const mapping = JSON.parse('(the above mapping definition)');
const verificationService = new VerificationService(mapping);
people.forEach(async (person) => {
try {
console.log('verifying person', JSON.stringify(person));
const verified = await verificationService.verifyAsync(person);
console.log('result', verified);
} catch (err) {
console.log('verification error', err);
}
});
node.js produces this output:
verifying person {"group":"adult","ageVerification":"A1","previousVaccineCertificate":"B1","medicalDocuments":null}
--> verifying firstDose: A1
--> verifying secondDose: A1, B1
verifying person {"group":"children","ageVerification":null,"previousVaccineCertificate":null,"medicalDocuments":null}
verifying person {"group":"old","ageVerification":"A3","previousVaccineCertificate":"B3","medicalDocuments":"C3"}
--> verifying firstDose: A3
--> verifying secondDose: A3, B3
--> verifying boosterDose: A3, B3, C3
verifying person {"group":"youth","ageVerification":"A4","previousVaccineCertificate":null,"medicalDocuments":null}
--> verifying firstDose: A4
result true
result true
result true
result true
The dummy return Promise.resolve(true); in the worker functions is async, but it has no delay. With actual asynchronous results, the output will be randomly ordered, but the above serves as a demo only anyway.

How to pull a range of objects from an array of objects and re-insert them at a new position in the array?

Desired Behaviour
Pull a range of objects from an array of objects and push them back to the array at a new index.
For example, pull objects from the array where their index is between 0 and 2, and push them back to the array at position 6.
For reference, in jQuery, the desired behaviour can be achieved with:
if (before_or_after === "before") {
$("li").eq(new_position).before($("li").slice(range_start, range_end + 1));
} else if (before_or_after === "after") {
$("li").eq(new_position).after($("li").slice(range_start, range_end + 1));
}
jsFiddle demonstration
Schema
{
"_id": ObjectId("*********"),
"title": "title text",
"description": "description text",
"statements": [
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
},
{
"text": "string",
"id": "********"
}]
}
What I've Tried
I am able to reposition a single object in an array of objects with the code below.
It uses pull to remove the object from the array and push to add it back to the array at a new position.
In order to do the same for a range of objects, I think I just need to modify the $pull and $push variables but:
I can't figure out how to use $slice in this context, either as a projection or an aggregation, in a $pull operation
Because I can't figure out the first bit, I don't know how to attempt the second bit - the $push operation
// define the topic_id to search for
var topic_id = request_body.topic_id;
// make it usable as a search query
var o_id = new ObjectID(topic_id);
// define the statement_id to search for
var statement_id = request_body.statement_id;
// define new position
var new_position = Number(request_body.new_position);
// define old position
var old_position = Number(request_body.old_position);
// define before or after (this will be relevant later)
// var before_or_after = request_body.before_or_after;
// define the filter
var filter = { _id: o_id };
// define the pull update - to remove the object from the array of objects
var pull_update = {
$pull: {
statements: { id: statement_id } // <----- how do i pull a range of objects here
}
};
// define the projection so that only the 'statements' array is returned
var options = { projection: { statements: 1 } };
try {
// perform the pull update
var topic = await collection.findOneAndUpdate(filter, pull_update, options);
// get the returned statement object so that it can be inserted at the desired index
var returned_statement = topic.value.statements[old_position];
// define the push update - to add the object back to the array at the desired position
var push_update = {
$push: {
statements: {
$each: [returned_statement],
$position: new_position
}
} // <----- how do i push the range of objects back into the array here
};
// perform the push update
var topic = await collection.findOneAndUpdate(filter, push_update);
}
Environments
##### local
$ mongod --version
db version v4.0.3
$ npm view mongodb version
3.5.9
$ node -v
v10.16.3
$ systeminfo
OS Name: Microsoft Windows 10 Home
OS Version: 10.0.18363 N/A Build 18363
##### production
$ mongod --version
db version v3.6.3
$ npm view mongodb version
3.5.9
$ node -v
v8.11.4
RedHat OpenShift Online, Linux
Edit
Gradually, figuring out parts of the problem, I think:
Using the example here, the following returns objects from array with index 0 - 2 (ie 3 objects):
db.topics.aggregate([
{ "$match": { "_id": ObjectId("********") } },
{ "$project": { "statements": { "$slice": ["$statements", 0, 3] }, _id: 0 } }
])
Not sure how to use that in a pull yet...
I also looked into using $in (even though i would prefer to just grab a range of objects than have to specify each object's id), but realised it does not preserve the order of the array values provided in the results returned:
Does MongoDB's $in clause guarantee order
Here is one solution to re-ordering results from $in in Node:
https://stackoverflow.com/a/34751295
Here an example with mongo 3.5
const mongo = require('mongodb')
;(async function (params) {
const client = await mongo.connect('mongodb://localhost:27017')
const coll = client.db('test').collection('test')
const from0to99 = Array(100).fill('0').map((_, i) => String(i))
const from5To28 = Array(24).fill('0').map((_, i) => String(i + 5))
const insert = { statements: from0to99.map(_ => ({ id: _ })) }
await coll.insertOne(insert)
const all100ElementsRead = await coll.findOneAndUpdate(
{ _id: insert._id },
{
$pull: {
statements: {
id: { $in: from5To28 }
}
}
},
{ returnOriginal: true }
)
/**
* It shows the object with the desired _id BEFORE doing the $pull
* You can process all the old elements as you wish
*/
console.log(all100ElementsRead.value.statements)
// I use the object read from the database to push back
// since I know the $in condition, I must filter the array returned
const pushBack = all100ElementsRead.value.statements.filter(_ => from5To28.includes(_.id))
// push back the 5-28 range at position 72
const pushed = await coll.findOneAndUpdate(
{ _id: insert._id },
{
$push: {
statements: {
$each: pushBack,
$position: 72 // 0-indexed
}
}
},
{ returnOriginal: false }
)
console.log(pushed.value.statements) // show all the 100 elements
client.close()
})()
This old issue helped
if you want "desired behavior" when mutating arrays ,
you add these to checklist:
array.length atleast==7 if you want to add ,splice at 6
creates a new array if u use concat
mutates orignal if used array.push or splice or a[a.length]='apple'
USE slice() to select between incex1 to index2.
or run a native for loop to select few elements of array or
apply a array.filter() finction.
once you select your elements which needed to be manupulated you mentioned you want to add it to end. so this is the method below.
about adding elements at end:
CONCAT EXAMPLE
const original = ['🦊']; //const does not mean its immutable just that it cant be reassigned
let newArray;
newArray = original.concat('🦄');
newArray = [...original, '🦄'];
// Result
newArray; // ['🦊', '🦄']
original; // ['🦊']
SPLICE EXAMPLE:
const zoo = ['🦊', '🐮'];
zoo.splice(
zoo.length, // We want add at the END of our array
0, // We do NOT want to remove any item
'🐧', '🐦', '🐤', // These are the items we want to add
);
console.log(zoo); // ['🦊', '🐮', '🐧', '🐦', '🐤']

Replacing an object in an object array in Redux Store using Javascript/Lodash

I have an object array in a reducer that looks like this:
[
{id:1, name:Mark, email:mark#email.com},
{id:2, name:Paul, email:paul#gmail.com},
{id:3,name:sally, email:sally#email.com}
]
Below is my reducer. So far, I can add a new object to the currentPeople reducer via the following:
const INITIAL_STATE = { currentPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_PERSON:
return {...state, currentPeople: [ ...state.currentPeople, action.payload]};
}
return state;
}
But here is where I'm stuck. Can I UPDATE a person via the reducer using lodash?
If I sent an action payload that looked like this:
{id:1, name:Eric, email:Eric#email.com}
Would I be able to replace the object with the id of 1 with the new fields?
Yes you can absolutely update an object in an array like you want to. And you don't need to change your data structure if you don't want to. You could add a case like this to your reducer:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => {
if (person.id === action.payload.id) {
return action.payload;
}
return person;
}),
};
This can be be shortened as well, using implicit returns and a ternary:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => (person.id === action.payload.id) ? action.payload : person),
};
Mihir's idea about mapping your data to an object with normalizr is certainly a possibility and technically it'd be faster to update the user with the reference instead of doing the loop (after initial mapping was done). But if you want to keep your data structure, this approach will work.
Also, mapping like this is just one of many ways to update the object, and requires browser support for Array.prototype.map(). You could use lodash indexOf() to find the index of the user you want (this is nice because it breaks the loop when it succeeds instead of just continuing as the .map would do), once you have the index you could overwrite the object directly using it's index. Make sure you don't mutate the redux state though, you'll need to be working on a clone if you want to assign like this: clonedArray[foundIndex] = action.payload;.
This is a good candidate for data normalization. You can effectively replace your data with the new one, if you normalize the data before storing it in your state tree.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
If you want to replace the old data with new data, you can do this-
entities.articles["1"] = newObj;
Use native splice method of array:
/*Find item index using lodash*/
var index = _.indexOf(currentPeople, _.find(currentPeople, {id: 1}));
/*Replace item at index using splice*/
arr.splice(index, 1, {id:1, name:'Mark', email:'mark#email.com'});

Resources