nodejs: save function in for loop, async troubles - node.js

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);
});
});
});

Related

How to do a query with every result of a query?

I'm trying to build an application, using MongoDB and Node.JS. I have 3 models: User, Ride, Participating.
Participating contains a userID and a rideID. It is almost as with a SQL logic: Participating links the two others models.
I'd like to, using a userID, return every Ride thanks to Participating Model
I tried to use a forEach, as the first request returns an array.
router.get('/getAllRide/:userID',function(req,res){
let userID = req.params.userID
let return = []
Participating.find({_idUser: userID })
.then(participating => {
participating.forEach(element => {
Ride.find({_id: element._id})
.exec()
.then(ride => {
retour.push(ride)})
});
res.status(200).json(return)
});
At the end of this code, the array return is empty, while it is supposed to contain every Ride whose _id is in an entity Participating.
OK, there are a couple of issues here:
return is a keyword. You probably shouldn't be using it as a variable name.
Database calls are asynchronous. forEach loops are synchronous. This means that you're immediately going to be returning retour (which looks undefined).
Mongoose has tools to populate nested relationships -- it's best not to do it in application code. Even if you are doing this in application code, it's likely best not to iterate over your results & do new finds -- instead, it's better to construct a single find query that returns all of the new documents you need.
If you did want to do this in application code, you'd want to either use async/await or Promise.all:
const toReturn = [];
const findPromises = participating.map(element => {
return Ride.find({_id: element._id})
.exec()
.then(result => toReturn.push(result)
});
return Promise.all(findPromises).then(() => res.status(200).json(toReturn));
(note: rather than using Promise.all, if you're using Bluebird you could instead use Promise.map.

In Cloud function how can i join from another collection to get data?

I am using Cloud Function to send a notification to mobile device. I have two collection in Firestore clientDetail and clientPersonalDetail. I have clientID same in both of the collection but the date is stored in clientDetail and name is stored in clientPersonal.
Take a look:
ClientDetail -- startDate
-- clientID
.......
ClientPersonalDetail -- name
-- clientID
.........
Here is My full Code:
exports.sendDailyNotifications = functions.https.onRequest( (request, response) => {
var getApplicants = getApplicantList();
console.log('getApplicants', getApplicants);
cors(request, response, () => {
admin
.firestore()
.collection("clientDetails")
//.where("clientID", "==", "wOqkjYYz3t7qQzHJ1kgu")
.get()
.then(querySnapshot => {
const promises = [];
querySnapshot.forEach(doc => {
let clientObject = {};
clientObject.clientID = doc.data().clientID;
clientObject.monthlyInstallment = doc.data().monthlyInstallment;
promises.push(clientObject);
});
return Promise.all(promises);
}) //below code for notification
.then(results => {
response.send(results);
results.forEach(user => {
//sendNotification(user);
});
return "";
})
.catch(error => {
console.log(error);
response.status(500).send(error);
});
});
}
);
Above function is showing an object like this
{clienId:xxxxxxxxx, startDate:23/1/2019}
But I need ClientID not name to show in notification so I'll have to join to clientPersonal collection in order to get name using clientID.
What should do ?
How can I create another function which solely return name by passing clientID as argument, and waits until it returns the name .
Can Anybody please Help.?
But I need ClientID not name to show in notification so I'll have to join to clientPersonal collection in order to get name using clientID. What should do ?
Unfortunately, there is no JOIN clause in Firestore. Queries in Firestore are shallow. This means that they only get items from the collection that the query is run against. There is no way to get documents from two top-level collection in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection.
How can I create another function which solely return name by passing clientID as argument, and waits until it returns the name.
So the most simple solution I can think of is to first query the database to get the clientID. Once you have this id, make another database call (inside the callback), so you can get the corresponding name.
Another solution would be to add the name of the user as a new property under ClientDetail so you can query the database only once. This practice is called denormalization and is a common practice when it comes to Firebase. If you are new to NoQSL databases, I recommend you see this video, Denormalization is normal with the Firebase Database for a better understanding. It is for Firebase realtime database but same rules apply to Cloud Firestore.
Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.
The "easier" solution would probably be the duplication of data. This is quite common in NoSQL world.
More precisely you would add in your documents in the ClientDetail collection the value of the client name.
You can use two extra functions in this occasion to have your code clear. One function that will read all the documents form the collection ClientDetail and instead of getting all the fields, will get only the ClientID. Then call the other function, that will be scanning all the documents in collection ClientPersonalDetail and retrieve only the part with the ClientID. Compare if those two match and then do any operations there if they do so.
You can refer to Get started with Cloud Firestore documentation on how to create, add and load documents from Firestore.
Your package,json should look something like this:
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"firebase-admin": "^6.5.1"
}
}
I have did a little bit of coding myself and here is my example code in GitHub. By deploying this Function, will scan all the documents form one Collection and compare the ClientID from the documents in the other collection. When it will find a match it will log a message otherwise it will log a message of not matching IDs. You can use the idea of how this function operates and use it in your code.

Express, Mongoose, db.findOne always returns same document

I am attempting a CRUD app with MEAN stack. I am using mongoose in Express to call to the MongoDB. I am using the FindOne function with a specified parameter, and it's always returning the same (incorrect) document.
I know I am connected to the correct database, since I get a document back from that collection, but it's always the same document, no matter what I pass as the parameter.
module.exports = mongoose.model('Player', playersSchema, 'players'); //in player.js
const Player = require('./model/players');
app.get('/api/player/:id', (req, res) =>{
Player.findOne({id: req.params.playerId},
function(err, player) {
if(err) {
res.json(err);
}
else {
res.json(player);
}
});
});
I have 3 separate "players", with three distinct "playerID" fields (38187361, 35167321, 95821442). I can use Postman to GET the following URL, for example:
http://localhost:3000/api/player/anythingyouWantInHere
and it will return 38187361, the first document. I've been over this website, many tutorials, and the Mongoose documentation and I can't see what I'm doing wrong..
I'd like to eventually find by playerId OR username OR email, but one hurdle at a time...
From the mongoose documentation of findOne, if you pass Id a null or an empty value, it will query db.players.findOne({}) internally which will return first document of the collection everytime you fetch. So make sure you are passing non-empty id here.
Note: conditions is optional, and if conditions is null or undefined,
mongoose will send an empty findOne command to MongoDB, which will
return an arbitrary document. If you're querying by _id, use
findById() instead.
Your route is '/api/player/:id', so the key on the req.params object will be 'id' not 'playerId'.
I don't know what/where/if you're populating the playerId param, but if you update your query to call req.params.id it should actually change the document based on the path as you seem to be wanting to do.
I had the same problem, and it was that the name of column's table was different from the model I had created.
In my model the name of the wrong column was "role" and in my table it was "rol".

Mongoose/node.js how to find, populate, do stuff, 'depopulate' and update

I want to make a nice and elegant call to db, but constantly suffer from lack of mongoose experience.
Hope i'm not too annoying, i'm trying to adress Stack Overflow only when my docs-google-Stack-digging skills fails.
What I want to do:
-find a doc in DB
-populate array inside this doc (this array in Schema is an array of Objectids from UserMeta Schema)
-send it throug sockets to some better places
-update found doc with another _id reffering to doc in UserMeta
-save this updates to db & as future reference to var currentroom
Problem occures in last 2 steps, as i can't 'unpopulate' doc, that i already got as response and update-save it further.
For the moment that is how i'm doing this without any population:
Room.findOne({ Roomid: roomid }, function (err, oldRoom) {
client.emit('others', oldRoom);
oldRoom.UsersMeta.push(currentUser._id);
oldRoom.save(function (err, newRoom) {
currentroom = newRoom;
});
})
I can just brute-force-ish copy needed docs through toJSON from parrent UserMeta to this Room doc and just manually maintain both of them. But if there is a way to do this automagically via handy mongoose tools, I would like to take this way. And in the sake of curiosity, of course.
It's a continuation of my previous question Saving reference to a mongoose document, after findOneAndUpdate -
just a remark, you dont really need to go there
Upd: Thing is that I need to run populate() In query with findOne, therefore in response I got oldRoom already with populated _ids
Room.findOne({ Roomid: roomid }).populate('UsersMeta').exec(function (err, oldRoom) {
if (err) {
console.log(err);
}
else if (oldRoom) {
console.log(oldRoom.UsersMeta)
client.emit('others', oldRoom.UsersMeta);
oldRoom.UsersMeta.push(currentUser._id);
oldRoom.save(function (err, newRoom) {
currentroom = newRoom;
console.log(newRoom);
});
}
else { console.log('nothing found') };
})
upd2: so i figured out, that if i push new _id in already populated oldroom and save it, in db it will automagically appear as set of just _id's as it should be. Yet I now confused if i will continue to work with this currentroom reference, as it was already pupulated, how can i safely remove something from populated array without removing populated entry from db completely.
upd3: Ah i just made a mess in my head. For some weird reason i thought that reference to doc saved in variable for each socket client will be always pointing to up-to-date doc in db, and that i will be able to work with this doc through it elluminating need to using find db tools more than once to get this reference... I need to rethink my db logic.
SO
There is a question then. If user connected to Rooms which is a doc from RoomSchema, and a user is a socket user i.e he has a personal scope in which i can store his personal session details. Can i somehow store direct link to this particular Room doc to elluminate need of searching for this room through whole db if user, for example, changes room's name. If i NEED to searh - it seems that it a better practice to save an id of room in which user is, and then just look up in db for room by this id and change it's name, am I right?
Here is the query to get UserMeta with ids only
Room.findOne({ Roomid: roomid },function (err, oldRoom) {
});

Mongoose.js conditional populate

I'm working with some old data where some of the schema has a "mixed" type.
Basically sometimes a value will be a referenced ObjectID, but other times it'll be some text (super poor design).
I unable to correctly populate this data because of the times a non-ObjectID appears.
So, for my actual question: Is it possible to create a populate (on a collection) that is conditional; I need to be able to tell the populate to skip those other values.
Yes, you can do that check the middleware function on the Mongoose API reference
http://mongoosejs.com/docs/middleware.html
What you need to do is before you populate those data, you validate the data if is is Object ID or not, if it is Object ID, you call next() to pass the next function, else you just return, this will skip it
Example
xSchema.pre('validate', function(next){
var x = this;
var checkXType = typeof x.id;
if (checkXType === String) {
return;
} else {
next();
}
});

Resources