Somehow I'm unable to update properties of a mongoose object I fetch from a MongoDB. I'm trying to follow this pattern: Mongoose Docs: Document
This is my code:
// note: getInstances just returns model.find()
let instances: InstanceDocument[] = await this.instanceService.getInstances();
instances.forEach(async (instance, index) => {
console.log(instance);
let deviceCount = await this.instanceService.getDeviceCount(instance._id);
let elementCount = await this.instanceService.getElementCount(instance._id)
instance.deviceCount = deviceCount;
instance.elementCount = elementCount;
await instance.save();
console.log(deviceCount, elementCount, instance);
})
The console.log prints the correct values for deviceCount and elementCount, but the instance object remains unmodified. It still has the unupdated values it has in the database.
Note: this is not a duplicate entry of Unable to add properties to js object, as I'm not trying to create a new object and give it properties.
Two things :
You can't use await inside an array method like forEach or map. It doesn't work (doesn't await). Use a for loop instead.
Mongoose has this weird requirement that you must explicitely tell it that a nested key has been modified in order to save it. See this question
let instances: InstanceDocument[] = await this.instanceService.getInstances();
for(let instance of instances) {
console.log(instance);
instance.deviceCount = await this.instanceService.getDeviceCount(instance._id);
instance.elementCount = await this.instanceService.getElementCount(instance._id);
instance.markModified("deviceCount"); // this
instance.markModified("elementCount"); // and this
await instance.save();
console.log(deviceCount, elementCount, instance);
}
The code above works. I made a mistake in defining the objects schema. I missed #Prop() decorator for the properties I added. This code works:
let instances: InstanceDocument[] = await this.instanceService.getInstances();
let fetchingDone = new Subject();
fetchingDone.subscribe(instances => res.json(instances))
instances.forEach(async (instance, index) => {
instance.deviceCount = await this.instanceService.getDeviceCount(instance._id);
instance.elementCount = await this.instanceService.getElementCount(instance._id);
await instance.save();
if (index+1 === instances.length) fetchingDone.next(instances);
})
Related
I am pre-fetching a product from a database using mongoose with next.js and react-query. I was wondering why I need to do a deep copy of a nested object in order to add a key:value to it. Otherwise it does not work. Let me know what I am not understanding.
await queryClient.prefetchQuery(['productSlug', slug], async () => {
const product = await read(slug);
const existingRatingObject = product.ratings.find(
(item) => item.postedBy.toString() === user._id.toString()
);
const copyProduct = JSON.parse(JSON.stringify(product));
if (existingRatingObject) {
copyProduct.star = existingRatingObject.star;
} else {
copyProduct.star = 0;
}
console.log({ copyProduct });
return JSON.stringify(copyProduct);
});
The reason is that the product fetched is a Mongoose document not a plain old JavaScript object.
When you convert it to plain old javascript Object, you will be able to add any key to it.
You can add .lean() to you query or add toObject/toJSON to you the fetched document
I'm new to the "async/await" aspect of JS and I'm trying to learn how it works.
The error I'm getting is Line 10 of the following code. I have created a firestore database and am trying to listen for and get a certain document from the Collection 'rooms'. I am trying to get the data from the doc 'joiner' and use that data to update the innerHTML of other elements.
// References and Variables
const db = firebase.firestore();
const roomRef = await db.collection('rooms');
const remoteNameDOM = document.getElementById('remoteName');
const chatNameDOM = document.getElementById('title');
let remoteUser;
// Snapshot Listener
roomRef.onSnapshot(snapshot => {
snapshot.docChanges().forEach(async change => {
if (roomId != null){
if (role == "creator"){
const usersInfo = await roomRef.doc(roomId).collection('userInfo');
usersInfo.doc('joiner').get().then(async (doc) => {
remoteUser = await doc.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
}
}
})
})
})
However, I am getting the error:
Uncaught (in promise) TypeError: Cannot read property 'joinerName' of undefined
Similarly if I change the lines 10-12 to:
remoteUser = await doc.data();
remoteNameDOM.innerHTML = `${remoteUser.joinerName} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser.joinerName}`;
I get the same error.
My current understanding is that await will wait for the line/function to finish before moving forward, and so remoteUser shouldn't be null before trying to call it. I will mention that sometimes the code works fine, and the DOM elements are updated and there are no console errors.
My questions: Am I thinking about async/await calls incorrectly? Is this not how I should be getting documents from Firestore? And most importantly, why does it seem to work only sometimes?
Edit: Here are screenshots of the Firestore database as requested by #Dharmaraj. I appreciate the advice.
You are mixing the use of async/await and then(), which is not recommended. I propose below a solution based on Promise.all() which helps understanding the different arrays that are involved in the code. You can adapt it with async/await and a for-of loop as #Dharmaraj proposed.
roomRef.onSnapshot((snapshot) => {
// snapshot.docChanges() Returns an array of the documents changes since the last snapshot.
// you may check the type of the change. I guess you maybe don’t want to treat deletions
const promises = [];
snapshot.docChanges().forEach(docChange => {
// No need to use a roomId, you get the doc via docChange.doc
// see https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange
if (role == "creator") { // It is not clear from where you get the value of role...
const joinerRef = docChange.doc.collection('userInfo').doc('joiner');
promises.push(joinerRef.get());
}
});
Promise.all(promises)
.then(docSnapshotArray => {
// docSnapshotArray is an Array of all the docSnapshots
// corresponding to all the joiner docs corresponding to all
// the rooms that changed when the listener was triggered
docSnapshotArray.forEach(docSnapshot => {
remoteUser = docSnapshot.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
});
});
However, what is not clear to me is how you differentiate the different elements of the "first" snapshot (i.e. roomRef.onSnapshot((snapshot) => {...}))). If several rooms change, the snapshot.docChanges() Array will contain several changes and, at the end, you will overwrite the remoteNameDOM and chatNameDOM elements in the last loop.
Or you know upfront that this "first" snapshot will ALWAYS contain a single doc (because of the architecture of your app) and then you could simplify the code by just treating the first and unique element as follows:
roomRef.onSnapshot((snapshot) => {
const roomDoc = snapshot.docChanges()[0];
// ...
});
There are few mistakes in this:
db.collection() does not return a promise and hence await is not necessary there
forEach ignores promises so you can't actually use await inside of forEach. for-of is preferred in that case.
Please try the following code:
const db = firebase.firestore();
const roomRef = db.collection('rooms');
const remoteNameDOM = document.getElementById('remoteName');
const chatNameDOM = document.getElementById('title');
let remoteUser;
// Snapshot Listener
roomRef.onSnapshot(async (snapshot) => {
for (const change of snapshot.docChanges()) {
if (roomId != null){
if (role == "creator"){
const usersInfo = roomRef.doc(roomId).collection('userInfo').doc("joiner");
usersInfo.doc('joiner').get().then(async (doc) => {
remoteUser = doc.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
}
}
}
})
I had a discord bot which used:
client.users.cache
This returns a Collection[Map] with all the users cached by the bot.
I added sharding and I want to do the same so I replaced it with:
client.shard.fetchClientValues('users.cache');
but this returns a array and I need a Collection[Map] that is the same as the one returned from:
client.users.cache
Does anyone know how to convert that array in a Collection[Map] the same as the one returned from:
client.users.cache
or how to get a Collection[Map] from all the shards that looks the same as the one returned from:
client.users.cache
I think you could create a new Collection, iterate over the array, and insert/set new elements by using the set() method. Just make sure the key is the user ID, and the data is the user object:
let results = await client.shard.fetchClientValues('users.cache');
let collection = new Discord.Collection();
results.forEach((users) => {
users.forEach((user) => {
collection.set(user.id, new Discord.User(client, user));
});
});
console.log(collection);
Collection is pretty similar to Map, so there is a chance that it's also can be constructed with an iterable of two-element arrays where the first element is the key and the second is the value:
let results = await client.shard.fetchClientValues('users.cache');
let collection = new Discord.Collection();
results.forEach((users) => {
let userCollection = new Discord.Collection(users.map((user) => [user.id, new Discord.User(client, user)]));
collection = collection.concat(userCollection);
});
console.log(collection);
There are questions on how to update nested properties for a Firebase record, but no answers on how to create records with nested properties.
This and this were similar but did not help.
From the web, the goal is to create a Firebase record with nested properties.
Using dot notation works for updates, but a nested hierarchy doesn't get created when reusing the same key for creating the record.
Which makes sense because the key doesn't impart any information about the data types of the child properties.
What is the right way to create an object with nested properties?
async test(serviceId, numCredits, emailAddress) {
// Set credits key.
let creditsKey = `credits.${serviceId}.numAllowed`;
try {
// Get user matching #emailAddress.
let user = await this.getUser(emailAddress);
// New user? Create database record.
if (!user) {
this.db_
.collection('users')
.add(
{
emailAddress: emailAddress,
[{creditsKey}]: numCredits
}
);
// Nope, user exists so update his/her record.
} else {
// Set update query.
let query = this.db_
.collection('users')
.where('emailAddress', '==', emailAddress);
// Run update query.
const querySnapshot = await query.get();
return querySnapshot.docs[0].ref.update({
[creditsKey]: firebase.firestore.FieldValue.increment(numCredits)
});
}
} catch(e) {
debug('Error in test(): ' + e);
}
}
If I correctly understand your question, the following would do the trick. (There are probably more elegant solutions however...)
const obj = {};
obj.numAllowed = numCredits;
const obj1 = {};
obj1[serviceId] = obj;
// ...
this.db_.collection('users')
.add(
{
emailAddress: emailAddress,
credits: obj1
})
How to loop data in RowDataPacket.
This problem happen because in addonis QueryBuild not return same value
as lucid models
If I use lucid models every work fine
const emp = await EMP.all();
for(let i in emp.rows) {
const data = emp.rows[i]
}
After I using querybuilder I do something like this
const emp = await Database
.table('emp');
for(let i in emp.RowDataPacket) {
console.log('s')
const data = emp.RowDataPacket[i]
const emp = await emp_sell.query()
.where('emp_id',data.id);
}
It's not even display 's'
When making this query await Database.table('emp');, you ended with an RowDataPacket objects, which is an object not iterable, as a workaround you could parse it to an array as:
JSON.parse(JSON.stringify(emp))
Further reading here.