Mongoose needs 2 times excecution to update collection - node.js

Something strange is going on or something idiotic I did but I got the following problem.
I got aenter code here web app where I have an online menu for a restaurant.
The structure of the products is as follows.
Facility->Category->Item->Name
So all item models have saved the name of the category they belong to as a string.
But sometimes you want to change the name of the category. What I wanted to do was find all the items in this category and change the name of the assigned category to the new one. Everything looked great until I saw that it took two times to run the controller that changed the name of the category and on the items to fully save the new name to the items.
The category changed the name but the items updated to the new name on the second run. Weird right?
So, what is that you can see that I don't and I implemented the silliest way of bugfix in the history of bugfixes.
Here is the controller - route.
module.exports.updateCtg = async(req,res)=>{
const {id} = req.params;
for(i=0;i<2; i++){
category = await CategoryModel.findByIdAndUpdate(id,{...req.body.category});
await category.save();
items = await ItemModel.find({});
for(item of items){
if(item.facility === category.facility){
item.category = category.name;
await single.save();
}
}
}
res.render('dashboard/ctgview', {category._id});
}

The findByIdAndUpdate function returns the found document, i.e. the document before any updates are applied.
This means that on the first run through category is set to the original document. Since the following loop uses category.name, it is setting the category of each item to the unmodified name.
The second iteration find the modified document, and the nested loop uses the new value in category.name.
To get this in a single pass, use
item.category = req.category.name;
or if you aren't certain it will contain a new name, use
item.category = req.category.name || category.name;
Or perhaps instead of a loop, use updateMany
if (req.category.name) {
ItemModel.updateMany(
{"item.facility": category.facility},
{"item.category": req.category.name}
)
}

Related

Update multiple dynamic nested Firestore fields using FieldPath

I am trying to update some Firestore documents in a batch in Nodejs. Some of the fields I'm updating are nested Map fields with periods in their names that are generated dynamically. I understand this has been covered before and the solution is:
var email = 'test#email.com';
var myPath = new admin.firestore.FieldPath('email', email);
batch.update(db.collection('collection').doc('document'), myPath, admin.firestore.FieldValue.delete());
This would delete the field "email.'test#email.com'". However, I'm trying to update multiple fields like this:
var email = 'test#email.com';
var myPath = new admin.firestore.FieldPath('email', email);
var updateObject = {[myPath]: admin.firestore.FieldValue.delete()};
updateObject = {...updateObject, count: admin.firestore.FieldValue.increment(1)};
batch.update(db.collection('collection').doc('document'), updateObject);
When I try this, the count field is updated, but the nested email field is unchanged. I'm assuming there is some issue with how I'm getting the FieldPath object in there. All the examples I can find only show updating one field at a time. There are also cases where I'll need to update multiple nested fields (such as two fields in the email map). How should this be done correctly?
I just ran this tiny test on a database on my own:
const docRef = admin.firestore().doc("68821373/i6ESA7AZwZRhPsdDHGmY");
const updates = {
toDelete: admin.firestore.FieldValue.delete(),
toIncrement: admin.firestore.FieldValue.increment(1)
};
docRef.update(updates);
This incremented the toIncrement field and removed the toDelete field. So the operations can be combined in a single call, although I am not sure how your code is different.
I also quickly ran a test with a batch, just in case that makes a difference:
const docRef = admin.firestore().doc("68821373/i6ESA7AZwZRhPsdDHGmY");
const batch = admin.firestore().batch();
const updates = {
toDelete: admin.firestore.FieldValue.delete(),
toIncrement: admin.firestore.FieldValue.increment(1)
};
batch.update(docRef, updates);
await batch.commit();
But here too, the increment and delete are both executed without problems for me.

Approach for changing fields in documents that are related to other documents

I am building an API and came across an issue that I have a few ideas of how to solve, but I was wondering what is the most optimal one. The issue is the following:
I have a Product model which has, for the sake of simplicity one field called totalValue.
I have another model called InventoryItems, which, whenever is updated, the totalValue of Product must also be updated.
For example, if the current totalValue of a product is say $1000, when someone purchases 10 screws at a cost of $1 each, a new InventoryItem record will be created:
InventoryItem: {
costPerItem: 1,
quantity: 10,
relatedToProduct: "ProductXYZ"
}
At the same time of creation of that item, totalValue of the respective ProductXYZ must be updated to now $1100.
The question is what is the most efficient and user-friendly way to do this?
Two ways come to my mind (and keep in mind that the code bellow is kinda pseudo, I have intentionally omitted parts of it, that are irrelevant for the problem at hand):
When the new InventoryItem is created, it also queries the database for the product and updates it, so both things happen in the same function that creates the inventory item:
function async createInventoryItem(req, res) {
const item = { ...req.body };
const newInventoryItem = await new InventoryItem({...item}).save();
const foundProduct = await Product.find({ name: item.relatedtoProduct }).exec();
foundProduct.totalValue = foundProduct.totalValue + item.costPerItem * item.quantity;
foundProduct.save();
res.json({ newInventoryItem, newTotalOfProduct: foundProduct.totalValue });
}
That would work, my problem with that is that I will no longer have "a single source of truth" as that approach will make it hard to update the code, as updating a given Product will be scattered all over the project.
The second approach that comes to my mind is that, when I receive the request to create the item, I do create the item, and then I make an internal request to the other endpoint that handles product updates, something like:
function async createInventoryItem(req, res) {
const item = { ...req.body };
const newInventoryItem = await new InventoryItem({...item}).save();
const totalCostOfNewInventoryItem = item.costPerItem * item.quantity;
// THIS is the part that I don't know how to do
const putResponse = putrequest("/api/product/update", {
product: item.relatedtoProduct,
addToTotalValue: totalCostOfNewInventoryItem
});
res.json({ newInventoryItem, newTotalOfProduct: putResponse.totalValue });
}
This second approach solves the problem of the first approach, but I don't know how to implement it, and it is I'm guessing a form of requests chaining or rerouting? Also I am guessing that the second approach will not have a performance penalty, since node will be sending requests to itself, so no time lost in accessing servers across the world or whatever)
I am pretty sure that the second approach is the one that I have to take (or is there another way that I am currently not aware of??? I am open to any suggestions, I am aiming for performance), but I am unsure of exactly how to implement it.

How do insert multiple entities and return them in TypeORM

Let's say i have a Vote entity. I want to insert an array with 5 votes simultaneously and return them. I have tried : await Vote.save(votes) but that doesnt work and it doesnt return them either. Any ideas?
Firstly you need to prepare entities instances (it's not just an object which you mast probably have made in some place):
const votesEntities = Vote.create(votes);
And then you could save these entities:
await Vote.save(votesEntities);
But I would advise to use an insert instead of save (because save forms a separate query to DB for each entity) and return previously prepared entities. How it might look in the end:
async insertVotes(votes) {
const votesEntities = Vote.create(votes);
await Vote.insert(votesEntities);
return votesEntities;
}

Airtable sometimes return results and sometimes not

I am using Node.js library to communicate with AirTable, official js library. I am having a problem with filterByFormula. Sometimes when I try to find record from AirTable I don’t get results even though I am pretty sure that there should be some results. My logic is such that if I do not find the record in AirTable I create a new one. If I find it I will update the existing one. This logic leads to problem with same entries entered twice.
This is my query:
getReservationByID: async (externalID) {
let reservations = await calendarBase('Reservations').select({
view: 'Main View',
filterByFormula: `{External ID} = \"${externalID}\"`,
maxRecords: 1,
}).all();
return (reservations.length > 0) ? reservations[0] : null;
},
And this function is used like this:
let oldReservation = await getReservationByID(reservation.id);
if (!oldReservation) {
createNewReservation(reservation);
} else {
updateReservation(reservation);
}
As you can see, it is essential for me to get record from AirTable if it exists, but sometimes AirTable is not returning an existing record with given ID and then I get duplicate with same external ID. Am I doing something wrong or is there some issue that I am not aware of?
I have managed to solve the problem. I have removed the view from query and now everything is working. If no view is included, Airtable will search all records in the table which match your formula. If you specify the view, Airtable will limit its search to just that view and then find all records that match your formula. If you have a filter on the view that you put in select, in my case 'Main View', then you may be hiding certain records from your script.
My new query looks like this:
getReservationByID: async (externalID) {
const reservations = await calendarBase('Reservations').select({
filterByFormula: `{External ID} = \"${externalID}\"`,
maxRecords: 1,
}).all();
return (reservations.length > 0) ? reservations[0] : null;
},

nodejs: save function in for loop, async troubles

NodeJS + Express, MongoDB + Mongoose
I have a JSON feed where each record has a set of "venue" attributes (things like "venue name" "venue location" "venue phone" etc). I want to create a collection of all venues in the feed -- one instance of each venue, no dupes.
I loop through the JSON and test whether the venue exists in my venue collection. If it doesn't, save it.
jsonObj.events.forEach(function(element, index, array){
Venue.findOne({'name': element.vname}, function(err,doc){
if(doc == null){
var instance = new Venue();
instance.name = element.vname;
instance.location = element.location;
instance.phone = element.vphone;
instance.save();
}
}
}
Desired: A list of all venues (no dupes).
Result: Plenty of dupes in the venue collection.
Basically, the loop created a new Venue record for every record in the JSON feed.
I'm learning Node and its async qualities, so I believe the for loop finishes before even the first save() function finishes -- so the if statement is always checking against an empty collection. Console.logging backs this claim up.
I'm not sure how to rework this so that it performs the desired task. I've tried caolan's async module but I can't get it to help. There's a good chance I'm using incorrectly.
Thanks so much for pointing me in the right direction -- I've searched to no avail. If the async module is the right answer, I'd love your help with how to implement it in this specific case.
Thanks again!
Why not go the other way with it? You didn't say what your persistence layer is, but it looks like mongoose or possibly FastLegS. In either case, you can create a Unique Index on your Name field. Then, you can just try to save anything, and handle the error if it's a unique index violation.
Whatever you do, you must do as #Paul suggests and make a unique index in the database. That's the only way to ensure uniqueness.
But the main problem with your code is that in the instance.save() call, you need a callback that triggers the next iteration, otherwise the database will not have had time to save the new record. It's a race condition. You can solve that problem with caolan's forEachSeries function.
Alternatively, you could get an array of records already in the Venue collection that match an item in your JSON object, then filter the matches out of the object, then iteratively add each item left in the filtered JSON object. This will minimize the number of database operations by not trying to create duplicates in the first place.
Venue.find({'name': { $in: jsonObj.events.map(function(event){ return event.vname; }) }}, function (err, docs){
var existingVnames = docs.map(function(doc){ return doc.name; });
var filteredEvents = jsonObj.events.filter(function(event){
return existingVnames.indexOf(event.vname) === -1;
});
filteredEvents.forEach(function(event){
var venue = new Venue();
venue.name = event.vname;
venue.location = event.location;
venue.phone = event.vphone;
venue.save(function (err){
// Optionally, do some logging here, perhaps.
if (err) return console.error('Something went wrong!');
else return console.log('Successfully created new venue %s', venue.name);
});
});
});

Resources