How to modify array data with foreach - javascript

I took data from MongoDB and executed it with forEach statement.
However, I want to change the date data from 2018-08-01T00:00:00.000Z' to 'YYYY-MM.
When I ran console.log using the Moment package of Nodejs, the data I wanted was printed out.
console.log(moment(repo.created_at).format("YYYY-MM")) code normally outputs the data I want, but the original data did not change when saved as repo.created.
I don't know how to solve this problem.
/* GET MyPage Page */
router.get(`/:userId/admin/mypage`, function (req, res, next) {
let userId = req.params.userId; // UserID Variable
Repo.find({ // MongoDB
'owner.login': userId
}, function (err, repo) {
if (err) throw err;
// console.log(repo);
repo.forEach(function (repo) {
repo.created_at = moment(repo.created_at).format("YYYY-MM");
console.log(repo.created_at)
repo.updated_at = moment(repo.updated_at).format("YYYY-MM");
console.log(repo.updated_at)
})
res.render('mypage/main', {
userId: userId,
dataArray: repo,
})
})
When I console.log it it's still like this.
2018-08-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2018-08-01T00:00:00.000Z
2018-09-01T00:00:00.000Z
2017-08-01T00:00:00.000Z
But with console.log(moment(repo.created_at).format("YYYY-MM")) code. it works fine
2018-09
2018-09
2018-09
2018-09
2018-09
2018-09
2018-08

I recommend to use a map function instead of forEach, because it is normally more performant and doesn't mutate the original array. (A short demonstration about the differences between map and forEach is given in the article "JavaScript — Map vs. ForEach
What’s the difference between Map and forEach in JavaScript?" at codeburst.io.)
Furthermore you should use a different name for your parameter names, because it can cause confusion about what is meant. This is called "scope" in JavaScript and it is also an example of "variable shadowing" or "name shadowing" and can cause subtle bugs. Therefor, you should use this with caution. In your case, you could name the loop parameter holding the array entries of your repo something like "repoElement", "repoItem", "repoEntry" or you find a more descriptive name.
You also say
but the original data did not change when saved as repo.created
If you do something like repoItem.created = moment(repoItem.created_at).format("YYYY-MM");, you add a new property to the repoItem-object, but the created_at property remains untouched. In the following example, I have named the formatted dates createdAt and updatedAt, so that these new properties get created. But every new repoItem will also have the untouched created_at and updated_at properties available next to the new properties createdAt and updatedAt.
// ...
const dataArray = repo.map(repoItem => ({
...repoItem,
createdAt: moment(repoItem.created_at).format("YYYY-MM"),
updatedAt: moment(repoItem.updated_at).format("YYYY-MM")
});
res.render('mypage/main', {
userId,
dataArray
})
// ...
If you don't want to include the originally formatted data, you have to make another step and destruct the data that you want before adding it to the new object. You can also destruct everything and then delete created_at and updated_at. This can be done like this:
const newRepoItem = { ...repoItem };
delete newRepoItem.created_at;
delete newRepoItem.updated_at;
or by using the "rest" parameter like this:
const { created_at, updated_at, ...newRepoItem } = repoItem;
// now, you can use the new variable newRepoItem.
More information about removing unwanted properties from an object can be found here: Remove value from object without mutation.
I would use the version that is better understood by you and your team. In my opinion, the keyword delete is the solution that is easier to understand for most people, because it literally says what it does. But if you like destructuring and are used to it, this can be the more compact and readable solution. Don't forget, that it could also be possible, that you only have a few properties in your repoItem that you care about. In this case, you can simply destructure the items that you want to keep. This could look like in the following example:
// ...
const dataArray = repo.map(repoItem => {
const { firstPropertyToKeep, secondPropertyToKeep } = repoItem;
return ({
firstPropertyToKeep,
secondPropertyToKeep,
createdAt: moment(repoItem.created_at).format("YYYY-MM"),
updatedAt: moment(repoItem.updated_at).format("YYYY-MM")
});
res.render('mypage/main', {
userId,
dataArray
})
I have create a REPL where you can see the difference between forEach and map: https://repl.it/#helgedrews/forEach-vs-map
I hope this helps a bit.

Just use .map()
Example:
const updatedRepos = repos.map((repo)=>{
return {
...repo,
created_at: moment(repo.created_at).format("YYYY-MM");
updated_at: moment(repo.updated_at).format("YYYY-MM");
}
})

use [].map instead or set variable to save the result

Related

Javascript find and map values based on scheme throwing undefined

I'm working with Javascript to build a mapping function, that, given a scheme should find the mapper's object's value within an application variable.
My attempt at doing this results in an undefined error and I'm trying to figure out what I need to change to make it work correctly, I've put together a JS fiddle that can be found here as well.
const application = {
state: {
outgoings: {
credit: 0,
food: 0
}
}
}
const mapper = {
"AppFoodSpend": "outgoings.food",
"AppCreditSpend": "outgoings.credit"
}
for (const field in mapper) {
console.log(application.state[mapper[field]])
// expected results should be credit 0 and food 0
}
For further context, I have an object called application which contains some fields, they may not always be in the same order. My mapper object contains some key/value pairs, and it's the value that I'd like to try and find in application. for for instance, my console log should be retrieving something like:
application.state.outgoings.food
application.state.outgoings.credit
They may not always be in this order, so I need to (on each iteration of the mapper) find the key in my application variable.
What do I need to change here?
Strings like 'outgoings.food' are not valid to navigate a data structure. You could use some "lenses" lib or write something like this...
const mapper = {
"AppFoodSpend": ["outgoings", "food"],
"AppCreditSpend": ["outgoings", "credit"]
}
for (const field in mapper) {
console.log(
mapper[field].reduce(
(ctx, path) => ctx[path],
application.state
)
)
}

Setting Socket.io room variables

I would like to store some information in the socket room variables, but am getting the following error: UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'host' of undefined
This is my code:
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
io.sockets.adapter.rooms[socket.roomId].isOpen = true;
io.sockets.adapter.rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
If I try to log socket.roomId it would return something like rBAhx0. And when I log io.sockets.adapter.rooms, I get the following:
Map {
'PX_o3Di9sp_xsD6oAAAB' => Set { 'PX_o3Di9sp_xsD6oAAAB' },
'rBAhx0' => Set { 'PX_o3Di9sp_xsD6oAAAB' }
}
However, when I try to log io.sockets.adapter.rooms[socket.roomId], it returns undefined. How can I fix this?
Socket.io made some breaking changes in the newest version and rooms cannot be accessed like they used to. It changed a lot of objects and arrays into Maps and Sets, you can see it in the logs you've posted.
Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
Accessing properties of a Map works differently than accessing properties of a normal Object. Example:
const myMap = new Map();
myMap.set("foo", "bar");
console.log(myMap["foo"]) // undefined
console.log(myMap.get("foo")) // bar
Same applies to Sets, however in your case querying this Set in particular is probably a wrong approach, as this set only holds a collection of ids, and not actual room objects. Even if you were to get a value out of the Set, you could not access it's properties (host, isOpen and players) since it is only a string.
The version 3.0 made accessing the list of all rooms directly impossible I'm afraid. However the adapter now has a property socketRooms, which can be used in place of it.
In order to access rooms of a socket easier, you should access them like so:
io.sockets.adapter.socketRooms(socketId);
However that would still just return a list of strings.
The simplest solution to this problem would be to create an external variable outside of the connection scope.
const rooms = {};
io.on('connection', (socket, next) => {
socket.on('create game', async () => {
console.log('Creating game...');
socket.username = await generateUsername();
socket.roomId = generateId();
socket.join(socket.roomId);
if (!rooms[socket.roomId]) rooms[socket.roomId] = {};
rooms[socket.roomId].host = socket.username;
rooms[socket.roomId].isOpen = true;
rooms[socket.roomId].players = [socket.username];
console.log('Game created! ID: ', socket.roomId);
});
}
As you can see in your own log, io.sockets.adapter.rooms is a Map object (in the later versions of socket.io), not a plain object. That means that a room is accessed with .get(), not with plain indexing such as what you are using:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
That simply won't work because there is no plain socket.roomId property on the Map.
If you wanted to get the room object, you would have to do this:
let roomObj = io.sockets.adapter.rooms.get(socket.roomId);
roomObj.host = socket.username;
In my opinion, I wouldn't mess with the room objects directly. We already know that socket.io has changed their design before, rendering any code that tried to use them as broken and in need of redesign. Instead, I'd probably just create my own data structure where I stored my own data and then I won't be dependent upon the whims of how socket.io changes their design. Accessing the room objects directly is not a committed design or committed API of socket.io. They can change how it works whenever they want and have, indeed, changed it recently.
After the breaking update from Socket.io of changing Array to Map.
A simple solution would be changing:
io.sockets.adapter.rooms[socket.roomId].host = socket.username;
to:
io.sockets.adapter.get(socket.roomId).host = socket.username;

Extract TImestamp from the result of a Function

So I have an interface like this:
interface Test {
// ...
created_at: Timestamp;
// ...
}
and I return an object from a function like this:
export const getTest = functions.https.onCall(async (data, context) => {
if (!context.auth) {
return null;
}
let snap = await admin
.firestore()
.collection('test')
.get();
return snap.docs.map(r => ({ // Add id to the output
id: r.id,
...r.data()
})[0];
});
So I should be getting the timestamp object in my application, but this is what I get in the chrome console, when I try to get the object from the function like so:
// Using AngularFireFunctions
let result: Test = await this.fns.httpsCallable<void, Test>('getTest')().toPromise();
This is the whole content of result.created_at:
When I call result.created_at.toDate() I get an ....created_at.toDate is not a function error.
What can I do to change this?
This is what I am stuck with right now:
result.created_at = new Date(result.created_at._seconds * 1000);
It looks like you’re expecting the callable function to retain the data type of the objects returned from it. That’s not quite how it works. The function will serialize the passed objects as JSON, losing all object type information, including the Timestamp type. The Timestamp is being serialized using its internal representation, which is what you’re seeing in the log output. I wouldn’t write code that depends on this, as it's obviously using hidden implementation details.
What you should probably do instead is convert that Timestamp into a normal JavaScript object, and use that in the returned result. Then, on the client side, you will need to write code that understands how you’ve chosen to represent that timestamp. It is extra work, but it insulates the client from knowing those private implementation details.
I suggest putting the seconds and nanoseconds part of the Timestamp into a plain old object, replacing the existing Timestamp field, then converting that object back into a Timestamp on the client using the Timestamp constructor that takes the seconds and nanos components as arguments.

How do I implement a find-or-create pattern in firestore

The firestore api has me a little mixed up in trying to have a repeatable pattern for find-or-create style functions. I'd like the canonical version to look like this:
// returns a promise resolving to a DocumentSnapshot (I think??)
function findOrCreateMyObject(myObject) {
return findMyObject(myObject.identifier).then(documentSnapshot => {
return (documentSnapshot)? documentSnapshot : createMyObject(myObject);
});
};
I'm not sure if DocumentSnapshot is the appropriate return from this, but I figure the caller may want to inspect or update the result, like this:
return findOrCreateMyObject({ identifier:'foo' }).then(documentSnapshot => {
console.log(documentSnapshot.data.someProperty);
return documentSnapshot.ref.update({ someProperty:'bar' });
});
Assuming I am right about that (please tell me if not), it means that both the find and create functions must return a DocumentSnapshot. This is easy enough for the find...
function findMyObject(identifier) {
let query = db.collection('my-object-collection').where('identifier','=='identifier);
return query.get().then(querySnapshot => {
return (querySnapshot.docs.length)? querySnapshot.docs[0] : null;
});
}
...but rather awkward for the create, and the the gist of my problem. I'd want to write create like this....
function createMyObject(myObject) {
// get db from admin
let collectionRef = db.collection('my-object-collection');
return db.collection('my-object-collection').doc().set(myObject);
}
But I cannot because DocumentReference set() resolves to a "non-null promise containing void". Void? I must read back the object I just wrote in order to get a reference to it? In other words, my idealized create needs to be rewritten to be slower and clunkier...
function createMyObject(myObject) {
// get db from admin
let collectionRef = db.collection('my-object-collection');
return db.collection('my-object-collection').doc().set(myObject).then(() => {
// must we query the thing just written?
return findMyObject(myObject.identifier); // frowny face
});
}
This makes my generic create longer (and unnecessarily so when doing just a create). Please tell me:
is DocumentSnapshot the right "currency" for these functions to traffic in?
Am I stuck with a set() and then another query when creating the new object?
Thanks!
EDIT As an example of where I would apply this, say I have customers, uniquely identified by email, and they have a status: 'gold', 'silver' or 'bronze'. My CRM system decides that someone identifying himself as doug#stevenson.com deserves 'silver' status. We don't know at this point wither Mr. Stevenson is a current customer, so we do a find-or-create...
findOrCreateCustomer({ email:'doug#stevenson.com' }).then(customer => {
customer.update({ status:'silver' });
});
I wouldn't just create, because the customer might exist. I wouldn't just update, because the customer might not exist, or might not meet some other criterion for the update.

Is there a way to update multiple documents to add certain number to one column without using loops

I'm using SailsJS with MongoDB and have a model like this:
module.exports = {
attributes: {
name : { type: 'string' },
openDate: { type: 'date' },
}
}
And there are hundreds of document in the database.
Now I have a array with a list of names. And needs to add 30 days to openDate whose name is the list.
And is there a way to do this without creating a loop and within the loop, geting the name and update the openDate one by one, but use one collections.update to fulfill that?
Currently I only have code to find all the document whose name is in the list, but not sure what to do next.
Accounts.find({
name:{ $in : nameList}
}).exec(function(err, data) {
});
Unless you can apply the same date to all of your records (which i believe is not what you want) then the answer is no. There is no mechanism to do that, but it should be (You or somebody should propose it on their github repo), at least to pass an array of ids to the criteria and a matching array of values as the second parameter of update, and according to the docs you can pass an array of values, but I've test it, and it throws an error (at least on mysql adapter), and there are no test to back up that functionality.
All examples use lodash , es6 arrow functions and assume you have a fn to increaseDates)
So if you can use the same value, then you could do this:
Accounts.find({name:{ $in : nameList}}).exec(function(err, data) {
// error handling here
var criteria = _.map(data, 'id'); // same as pluck
Accounst.update(criteria, {openDate: increaseDate(x.openDate)}).exec(...);
});
Guessing that the above is not an option, you would need to use something like async series or parallel, likes this:
Accounts.find({name:{ $in : nameList}}).exec(function(err, records) {
// error handling here
tasks = _.map(records, (x)=> Accounts.update(x.id, {openDate: increaseDate(x.openDate)}, callback));
async.parallel(tasks, function(err, results){});
});
In my case i use async/await so is a matter of doing this and the updates will run in sequence:
async(()=> {
records = await (Accounts.find({name:{ $in : nameList}});
_.each(records, (x)=> await (Accounts.update(x.id, {openDate: increaseDate(x.openDate)}));
})()
or to run them in parallel, something like this
async(()=> {
records = await (Accounts.find({name:{ $in : nameList}});
tasks = _.map(records, (x)=> Accounts.update(x.id, {openDate: increaseDate(x.openDate)}));
await (tasks)
})()

Categories

Resources