Running a forEach() on an Array of Objects After Retrieving with findOne() in Mongo Back-end - arrays

I am using findOne() to retrieve a document like this:
let staffToUpdate = await Staff.findOne({
_id: request.parameters.id
}).exec();
let historyArray = await crewToUpdate.history;
console.log("historyArray: ", await historyArray);
console.log(Array.isArray(historyArray)); // returns true
The data looks like this:
history: [
{
status: "active",
startDate: <Date>,
endDate: <Date>,
completed: false
},
{
status: "training",
startDate: <Date>,
endDate: <Date>,
completed: true
}
]
When I do the above I get an array of objects printed out, as well as a return of "true" on the check to see if "historyArray" is indeed an array.
So now that I have this array, I'd like to run a transformation on the objects found within it, like so:
let updatedHistoryArray = historyArray.then(
updatedHistoryArray.forEach(history => {
history.completed = true;
history.endDate = new Date();
})
);
However, this is the part that's not working. When I try this I get this error:
Reason: ReferenceError: historyArray is not defined
What am I missing here?
UPDATE: After a suggestion from a commenter below, I tried this:
let staffToUpdate = await Staff.findOne({
_id: request.parameters.id
}).exec();
let staffObject = staffToUpdate.toObject();
let historyArray = await staffObject.history;
console.log(await historyArray); // prints the array
console.log(Array.isArray(historyArray)); // returns true
historyArray.forEach(history => { // this is where the error occurs
history.completed = true;
history.endDate = new Date();
});
With this last block of code I get this error:
Reason: ReferenceError: historyArray is not defined

historyArray is not a Promise and you can not run then on it.
When this code runs
let staffToUpdate = await Staff.findOne({
_id: request.parameters.id
}).exec();
it waits until query is executed and assigns the actual result (mongoose Document), not a promise and assigns it to staffToUpdate. You need to run toObject() on mongoose Document to get plain object without the wrapper:
const unwrappedStaffToUpdate = staffToUpdate.toObject();
After that you don't need to use await on crewToUpdate.history because it is not a Promise and it is synchronious. That is why you can not run then on historyArray because it is a normal Array and not a Promise.
Try this code:
unwrappedStaffToUpdate.historyArray.forEach(history => {
history.completed = true;
history.endDate = new Date();
});
Or if you do not want to mutate your Array use map instead of forEach:
const updatedHistoryArray = unwrappedStaffToUpdate.historyArray.map(history => ({
...history
completed: true;
endDate: new Date()
})
);

Related

Array returning undefined in Vue from indexedDB

The console.log in my indexedDB works and returns the result that I want; an array of objects that is currently in the store. So my code there is correct. I'm going to use this information to build a table. However, in Vue it returns undefined. I'm trying to set the leagues array in Vue to equal the result array that indexedDB gives, but it returns undefined.
This is the code in Vue:
<script>
import * as db from "../db/db.js";
export default {
name: "leaguesTable",
data: function() {
return {
leagues: []
};
},
created: function() {
this.leagues = db.getAllInStore("meta", "leagues");
console.log(this.leagues);
}
};
</script>
This is my indexedDB code:
function getAllInStore(dbName, storeName) {
let db;
var request = indexedDB.open(dbName, 1);
request.onerror = function(event) {
alert("Database error" + event.target.errorCode);
};
request.onsuccess = function(event) {
db = event.target.result;
let tx = db.transaction(storeName, "readonly");
tx.onerror = function(event) {
alert("Transaction error" + event.target.errorCode);
};
let store = tx.objectStore(storeName);
let result = store.getAll();
tx.oncomplete = function() {
alert("This should work");
console.log(result.result);
return result.result;
};
};
}
In your created hook you need to make sure to return a value from db.getAllInStore so that this.leagues assumes that value.
Next, In the getAllInStore function result.result gets returned from the transaction but not within onComplete or the enclosing getAllInStore function.
Since the db uses event hooks like onError and onComplete, Returning the request won't give you the result of the call to the db. In order to return the value of an async operation in javascript, typically callbacks or promises are used. The example below makes use of promises to solve the issue.
Vue JS:
<script>
import * as db from "../db/db.js";
export default {
name: "leaguesTable",
data: function() {
return {
leagues: []
};
},
// async is necessary to use await
created: async function() {
// await is es2016 syntactic sugar for retrieving the value of a promise
this.leagues = await db.getAllInStore("meta", "leagues");
console.log(this.leagues);
}
};
</script>
IndexDB:
function getAllInStore(dbName, storeName) {
// resolve param is a function that signifies a successful operation
// reject param is a function that should be called whenever a check or error occurs
return new Promise((resolve, reject) => {
let db;
let request = indexedDB.open(dbName, 1);
request.onerror = (event) => reject(event);
request.onsuccess = (event) => {
db = event.target.result;
let tx = db.transaction(storeName, "readonly");
request.onerror = (event) => reject(event);
let store = tx.objectStore(storeName);
let result = store.getAll();
tx.oncomplete = (result) => resolve(result.result);
};
});
}
Further Reading:
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises

Can't figure out why array length is undefined

I keep receiving the following message in my console, "Uncaught (in promise) TypeError: Cannot read property 'length' of undefined"
quotesData.quotes should be the key for an array, however, so I'm unsure as to why its length property is undefined.
quotesData should be a JSON object that looks like: { "quotes": [Object1, Object2, ...etc.]}
Is there something wrong with how I'm using axios? I'm still very new to programming in general and quite new to react.js
getQuote() {
let _this = this;
_this.serverRequest =
axios
.get("https://raw.githubusercontent.com/dwgillette/quotes/master/library")
.then(function(quotesData) {
console.log(quotesData);
let newQuote = quotesData.quotes[Math.floor(Math.random() * quotesData.quotes.length)];
_this.setState({
quote: newQuote.quote,
author: newQuote.author
});
})
}
So the data you want is actually going to be on a .data attribute of the response. So if you fix your code up like this you will be good to go :)
getQuote() {
let _this = this;
_this.serverRequest =
axios
.get("https://raw.githubusercontent.com/dwgillette/quotes/master/library")
.then(function(q) {
quotesData = q.data;
console.log(quotesData);
let newQuote = quotesData.quotes[Math.floor(Math.random() * quotesData.quotes.length)];
_this.setState({
quote: newQuote.quote,
author: newQuote.author
});
})
}
Because the promise resolves to response object. Try doing:
getQuote() {
let _this = this;
_this.serverRequest =
axios
.get("https://raw.githubusercontent.com/dwgillette/quotes/master/library")
.then(function(response) {
let newQuote = response.data.quotes[Math.floor(Math.random() * response.data.quotes.length)];
_this.setState({
quote: newQuote.quote,
author: newQuote.author
});
})
}
Here is a screenshot of the response object you are getting back.
I refactored your code to work. you need to use res.data.
getQuote = () => {axios.get("https://raw.githubusercontent.com/dwgillette/quotes/master/library")
.then(res => {
let newQuote =
res.data.quotes[Math.floor(Math.random() * res.data.quotes.length)];
this.setState({
quote: newQuote.quote,
author: newQuote.author
});
});
};

Parsing dynamic CSV through Node and writing schema in Mongo [duplicate]

Currently I need to push a large CSV file into a mongo DB and the order of the values needs to determine the key for the DB entry:
Example CSV file:
9,1557,358,286,Mutantville,4368,2358026,,M,0,0,0,1,0
9,1557,359,147,Wroogny,4853,2356061,,D,0,0,0,1,0
Code to parse it into arrays:
var fs = require("fs");
var csv = require("fast-csv");
fs.createReadStream("rank.txt")
.pipe(csv())
.on("data", function(data){
console.log(data);
})
.on("end", function(data){
console.log("Read Finished");
});
Code Output:
[ '9',
'1557',
'358',
'286',
'Mutantville',
'4368',
'2358026',
'',
'M',
'0',
'0',
'0',
'1',
'0' ]
[ '9',
'1557',
'359',
'147',
'Wroogny',
'4853',
'2356061',
'',
'D',
'0',
'0',
'0',
'1',
'0' ]
How do I insert the arrays into my mongoose schema to go into mongo db?
Schema:
var mongoose = require("mongoose");
var rankSchema = new mongoose.Schema({
serverid: Number,
resetid: Number,
rank: Number,
number: Number,
name: String,
land: Number,
networth: Number,
tag: String,
gov: String,
gdi: Number,
protection: Number,
vacation: Number,
alive: Number,
deleted: Number
});
module.exports = mongoose.model("Rank", rankSchema);
The order of the array needs to match the order of the schema for instance in the array the first number 9 needs to always be saved as they key "serverid" and so forth. I'm using Node.JS
You can do it with fast-csv by getting the headers from the schema definition which will return the parsed lines as "objects". You actually have some mismatches, so I've marked them with corrections:
const fs = require('mz/fs');
const csv = require('fast-csv');
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const rankSchema = new Schema({
serverid: Number,
resetid: Number,
rank: Number,
name: String,
land: String, // <-- You have this as Number but it's a string
networth: Number,
tag: String,
stuff: String, // the empty field in the csv
gov: String,
gdi: Number,
protection: Number,
vacation: Number,
alive: Number,
deleted: Number
});
const Rank = mongoose.model('Rank', rankSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
let headers = Object.keys(Rank.schema.paths)
.filter(k => ['_id','__v'].indexOf(k) === -1);
console.log(headers);
await new Promise((resolve,reject) => {
let buffer = [],
counter = 0;
let stream = fs.createReadStream('input.csv')
.pipe(csv({ headers }))
.on("error", reject)
.on("data", async doc => {
stream.pause();
buffer.push(doc);
counter++;
log(doc);
try {
if ( counter > 10000 ) {
await Rank.insertMany(buffer);
buffer = [];
counter = 0;
}
} catch(e) {
stream.destroy(e);
}
stream.resume();
})
.on("end", async () => {
try {
if ( counter > 0 ) {
await Rank.insertMany(buffer);
buffer = [];
counter = 0;
resolve();
}
} catch(e) {
stream.destroy(e);
}
});
});
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
As long as the schema actually lines up to the provided CSV then it's okay. These are the corrections that I can see but if you need the actual field names aligned differently then you need to adjust. But there was basically a Number in the position where there is a String and essentially an extra field, which I'm presuming is the blank one in the CSV.
The general things are getting the array of field names from the schema and passing that into the options when making the csv parser instance:
let headers = Object.keys(Rank.schema.paths)
.filter(k => ['_id','__v'].indexOf(k) === -1);
let stream = fs.createReadStream('input.csv')
.pipe(csv({ headers }))
Once you actually do that then you get an "Object" back instead of an array:
{
"serverid": "9",
"resetid": "1557",
"rank": "358",
"name": "286",
"land": "Mutantville",
"networth": "4368",
"tag": "2358026",
"stuff": "",
"gov": "M",
"gdi": "0",
"protection": "0",
"vacation": "0",
"alive": "1",
"deleted": "0"
}
Don't worry about the "types" because Mongoose will cast the values according to schema.
The rest happens within the handler for the data event. For maximum efficiency we are using insertMany() to only write to the database once every 10,000 lines. How that actually goes to the server and processes depends on the MongoDB version, but 10,000 should be pretty reasonable based on the average number of fields you would import for a single collection in terms of the "trade-off" for memory usage and writing a reasonable network request. Make the number smaller if necessary.
The important parts are to mark these calls as async functions and await the result of the insertMany() before continuing. Also we need to pause() the stream and resume() on each item otherwise we run the risk of overwriting the buffer of documents to insert before they are actually sent. The pause() and resume() are necessary to put "back-pressure" on the pipe, otherwise items just keep "coming out" and firing the data event.
Naturally the control for the 10,000 entries requires we check that both on each iteration and on stream completion in order to empty the buffer and send any remaining documents to the server.
That's really what you want to do, as you certainly don't want to fire off an async request to the server both on "every" iteration through the data event or essentially without waiting for each request to complete. You'll get away with not checking that for "very small files", but for any real world load you're certain to exceed the call stack due to "in flight" async calls which have not yet completed.
FYI - a package.json used. The mz is optional as it's just a modernized Promise enabled library of standard node "built-in" libraries that I'm simply used to using. The code is of course completely interchangeable with the fs module.
{
"description": "",
"main": "index.js",
"dependencies": {
"fast-csv": "^2.4.1",
"mongoose": "^5.1.1",
"mz": "^2.7.0"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Actually with Node v8.9.x and above then we can even make this much simpler with an implementation of AsyncIterator through the stream-to-iterator module. It's still in Iterator<Promise<T>> mode, but it should do until Node v10.x becomes stable LTS:
const fs = require('mz/fs');
const csv = require('fast-csv');
const streamToIterator = require('stream-to-iterator');
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
const rankSchema = new Schema({
serverid: Number,
resetid: Number,
rank: Number,
name: String,
land: String,
networth: Number,
tag: String,
stuff: String, // the empty field
gov: String,
gdi: Number,
protection: Number,
vacation: Number,
alive: Number,
deleted: Number
});
const Rank = mongoose.model('Rank', rankSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
let headers = Object.keys(Rank.schema.paths)
.filter(k => ['_id','__v'].indexOf(k) === -1);
//console.log(headers);
let stream = fs.createReadStream('input.csv')
.pipe(csv({ headers }));
const iterator = await streamToIterator(stream).init();
let buffer = [],
counter = 0;
for ( let docPromise of iterator ) {
let doc = await docPromise;
buffer.push(doc);
counter++;
if ( counter > 10000 ) {
await Rank.insertMany(buffer);
buffer = [];
counter = 0;
}
}
if ( counter > 0 ) {
await Rank.insertMany(buffer);
buffer = [];
counter = 0;
}
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Basically, all of the stream "event" handling and pausing and resuming gets replaced by a simple for loop:
const iterator = await streamToIterator(stream).init();
for ( let docPromise of iterator ) {
let doc = await docPromise;
// ... The things in the loop
}
Easy! This gets cleaned up in later node implementation with for..await..of when it becomes more stable. But the above runs fine on the from the specified version and above.
By saying #Neil Lunn need headerline within the CSV itself.
Example using csvtojson module.
const csv = require('csvtojson');
const csvArray = [];
csv()
.fromFile(file-path)
.on('json', (jsonObj) => {
csvArray.push({ name: jsonObj.name, id: jsonObj.id });
})
.on('done', (error) => {
if (error) {
return res.status(500).json({ error});
}
Model.create(csvArray)
.then((result) => {
return res.status(200).json({result});
}).catch((err) => {
return res.status(500).json({ error});
});
});
});

How to assign snap.val() to the global variable?

I want to assign snap.val() to this.Productslike this.Products= snap.val(); but this.Products is undefined in that scope.
Products: FirebaseListObservable<any>;
constructor(){
}
ionViewDidLoad(){
this.angularFire.database.list('/Products').$ref.orderByChild('uid')
.equalTo('NW1Kq4WB7ReUz2BNknYWML9nF133').on('child_added', function(snap){
console.log(snap.val().name);
//this.Products= snap.val();
});
}
I tried the following code when snap is returned ,but I receive this message -- No index defined for uid:
snap.forEach(SnapShot=>{
console.log(SnapShot.val().name)
My Firebase database:
"Products" : {
"-Kbx0i-TFeTyRbNZAZ_8" : {
"category" : "1",
"detail" : "xxxxx details",
"name" : "xxxxx",
"uid" : "NW1Kq4WB7ReUz2BNknYWML9nF133"
}
Please help. Thanks.
The directly answer the question you asked, you can use an ES6 arrow function:
let query = this.angularFire.database.list('/Products').$ref.orderByChild('uid')
.equalTo('NW1Kq4WB7ReUz2BNknYWML9nF133');
query.on('child_added', (snap) => this.Products= snap.val());
Or for ES5 compatibility, declare this as a variable:
let self = this;
let query = this.angularFire.database.list('/Products').$ref.orderByChild('uid')
.equalTo('NW1Kq4WB7ReUz2BNknYWML9nF133');
query.on('child_added', function(snap) {
self.Products= snap.val();
});
But in reality, this is an XY problem and you don't want what you think you want here.
What you've done is reimplement the list yourself, and defeat the entire purpose of AngularFire2, which handles all this synchronization on your behalf.
Additionally, you've mis-used child_added by assigning each record you get back (you get an array of results, not exactly one) to this.products, when you probably wanted to set this.products = [] and then use this.products.push(snap.val()) for each child_added invocation.
So what you really want here, is to use AngularFire's built-in queries and avoid this entire mess :)
this.products = af.database.list('/Products', {
query: {
orderByChild: 'uid',
equalTo: 'NW1Kq4WB7ReUz2BNknYWML9nF133'
}
});
I did it in this way:
import firebase from "firebase";
const firebaseConfig = {
your firebaseConfig...
};
let app = firebase.initializeApp(firebaseConfig);
let database = firebase.database();
export async function readFromFirebase(userId, key) {
const ref = database.ref("users/" + userId + "/" + key);
const snapshot = await ref.once("value");
return snapshot.val();
}
async function main() {
console.log(await readFromFirebase(109512127, "userName"));
}
main();

update in firebase is not working(with React) [duplicate]

I am quite new to firebase and i am trying to retrieve particular data. and getting this error,
Please help me with this.
My code looks like this
var t1= this.password.value;
var user_id = this.state.comments[2];
firebase.database().ref(user_id).orderByChild("Password").equalTo(t1).on("child_added", (snapshot) => {
var datas1 = []
snapshot.forEach((data1) =>
{
datas1.push( {
username: data1.val().Username,
password: data1.val().Password,
});
});
this.setState ({
datas1: datas1,
});
});
console.log(this.state.datas1);
even the array datas1 is coming empty.

Resources