Strange result when nesting query of two different collections - node.js

I have a need to check two separate collections in Mongo to see if a phone number exists.
I first created a global variable called 'ownerId'
I then look in one collection call 'Profile'. If the email value I pass exists in the 'emails' array of a document in that collection, I fill the 'ownerId' variable I created with a value in that document called 'owner_id'.
I then look in another collection called 'User', which has a single email field. If the email value I pass exists in a document in that collection, I fill the 'ownerId' variable I created with the '_id' of that document.
I have my queries nested in a couple 'then()' statements.
Here is my code:
Profile.findOne({'emails.email_address':req.body.invited_email}, function(err, result){
if(result)
ownerId = result.owner_id;
}).then(function(){
User.findOne({'email':req.body.invited_email}, function(err, result2){
if(ownerId !== null)
ownerId = result2._id;
})
}).then(function(){
console.log(' --------------- THIS IS AN EXISTING EMAIL OWNER ID: ' + ownerId);
})
The result is not as I expect.
If the 'Profile' query is true and finds a match, then it will console log the ownerId with a value.
If the second 'User' query is true, but there is not match for the 'Profile' it will console log 'null'. I expect it to console log the _id value of the User result.
Can anyone see the error in my logic?

I wouldn't mix callbacks and promises. I would recommend using async/await for this. It makes asynchronous code look synchronous. For example:
let owner_id;
try {
let profile = await Profile.findOne({ 'emails.email_address': req.body.invited_email });
let user = await User.findOne({ 'email': req.body.invited_email }):
if (profile) {
owner_id = profile.owner_id;
} else if (user) {
owner_id = user.owner_id;
} else {
console.log('no owner id found');
}
} catch(err) {
console.log(err);
}
The code is wrapped in try/catch to handle errors, just like the catch() method for ordinary promises. The functions using await needs the async keyword.
await is used to wait for promises to finish. No callbacks are needed. You can see the code looks like ordinary synchronous code.

Related

findByIdAndUpdate adding another document to the database rather than updating

I am using MEAN stack for patient CRUD operations. The update does not seem to be working properly. It adds another document to the database with the updated info but with a null id and leaves the old document that is supposed to be updated as is.
below is the code I wrote in the service for update patient
editPatient(id:string,patient: Patient){
const headers = { 'content-type': 'application/json'}
const body=patient;
console.log(body)
let url=environment.PATIENT_BASE_URL+environment.PATIENT.UPDATE_PATIENT + "?userId=" +id;
return this.httpClient.put(url, body);
}
Those are the contents of the environment file
export const environment = {
production: false,
BASE_URL:'http://localhost:3000',
PATIENT_BASE_URL:'http://localhost:3000/patients/',
PATIENT:{
GET_ALL_PATIENTS: 'list',
GET_PATIENT: 'view',
UPDATE_PATIENT: 'update',
DELETE_PATIENT: 'delete',
SEARCH_PATIENT: 'search',
ADD_PATIENT: 'add',
}
};
This is the code in patients.js
router.put('/update', function(req, res, next) {
const userId = req.body.userId;
let firstnameVal = req.body.firstName;
let lastnameVal = req.body.lastName;
let usernameVal = req.body.username;
let emailVal = req.body.email;
let birthDateVal = req.body.birthDate;
let genderVal = req.body.gender;
let patientObj = {
firstName: firstnameVal,
lastName: lastnameVal,
username: usernameVal,
email: emailVal,
birthDate : birthDateVal,
gender: genderVal
};
// patientsModel.update({'gender':'female'}, )
patientsModel.findByIdAndUpdate(userId, patientObj,{upsert: true, new: true} ,function(err, patientResponse){
if(err){
res.send({status:500, message: 'Unable to update the patient'});
}
else{
res.send({status:200, message: 'User updated successfully' ,results: patientResponse});
}
});
});
Because you used this option
upsert: true
If item with id not found it creates a new document
you can read the docs here
Using the upsert option, you can use findOneAndUpdate() as a
find-and-upsert operation. An upsert behaves like a normal
findOneAndUpdate() if it finds a document that matches filter. But, if
no document matches filter, MongoDB will insert one by combining
filter and update as shown below.
The second argument to findByIdAndUpdate is an update object. If it does not contain any update operators, it is treated as a replacement document.
If your intent is to replace the entire document so the only fields it contains are the ones provided in this function, add the _id to the object:
let patientObj = {
_id: new mongoose.types.ObjectId(userId),
firstName: firstnameVal,
...
If the intent is to modify the provided fields but leave any others fields alone, use the $set update operator like
patientsModel.findByIdAndUpdate(userId, {"$set": patientObj}, ...
There are a few problems with your code - as others have said:
the use of upsert: true is suspicious - I can't imagine when you'd want to upsert this, and
the lack of $set is also unusual unless the patientObj represents the entire document you wish to set
both these items are causing you issues, but I suspect your main problem is actually that your ID doesn't match anything.
You mention an auto-generated ID. Mongo uses an ObjectId (though mongoose perhaps does not) - depending on how you serialise this value, the string representation of it would probably look like this: 63b310df2b36d95e156a237d - however when you query for that value (as you do with userId) - it will return no matches, since you need to convert it to an object ID:
userId = new mongoose.types.ObjectId(req.body.userId)
You should also fix items 1 and 2 above.

How to use mongoose "populate" to specify the path for already existing document of different collection?

I am using Apollo Graphql, Express-Nodejs,MongoDB and Mongoose. I have 2 collection namely: Business and Order.
Here are the models
Here are the graphql types:
Here are the mutation:
createBusiness(
name: String,
address: String,
): Business
createOrder(
orderNumber: String,
businessName: String,
additionalDetails: String
): Order
A particular Business can have multiple orders, A particular order must have one particular Business.
What I want to do is to create an order for Business document.
Case 1.) If the Business document doesn't exists: then the createOrder mutation should create new Business document (by using populate)
Case 2.) But If the Business document exists, then the createOrder mutation should not create new Business document and only add new order and the reference to the existing Business document.
Could someone please let me know how can I fulfill the above in the graphql and mongoose ? Any suggestion would be helpful !
Here is my Order mutation resolver ( Its not working, not sure why !! )
import Order from '../models/Order';
import Business from '../models/Business';
export default {
Mutation:{
createOrder(_, {
orderNumber,
additionalDetails,
businessName
}){
return Business.findOne({
businessName: businessName
})
.then((exist)=>{
if (!exist){
let business_Name = new Business({
name: businessName
})
business_Name.save(function (err){
if (err) return handleError(err);
let order = new Order({
orderNumber: orderNumber,
businessName: business_Name._id,
additionalDetails: additionalDetails
});
order.save(function (err){
if (err) return handleError(err);
});
});
}
if (exist){
// WHAT SHOULD I DO FOR THIS CASE ??
}
});
},
}
}
Thanks in advance !
I slightly modified the logic for the case 1 such that if Business name doesn't exist then one should not be allowed to create Order. As if someone unauthorized is allowed to create business in the order mutation, then we may have to handle additional optional arguments(only for case 1) making the "createOrder" mutation to be more cumbersome and not so logical.
Rather we would inform the client user with some useful msg for the case 1 and for the case 2 when the business exist, we would:
1.) First create new Order then push it to the "orders" list of type Business and then save it (As the Type Business needs this reference to its child array of orders) (Read this: Saving Refs to Children )
2.) Then its time to save the newly created order and populate "businessReference".
Here is the complete code of createOrder mutation...
createOrder: async(_, {
orderNumber,
additionalDetails,
businessName
})=>{
// Check if the business name exists or not
try {
const business_name = await Business.findOne({
name: businessName
})
if (!business_name){
throw new Error ('Business name not found. Please create the Business first !');
}
// if the business name exists, then
// first create order
let order = await new Order({
orderNumber: orderNumber,
businessReference: business_Name._id,
additionalDetails: additionalDetails
})
business_name.orders.push(order); // then push this order to child
business_name.save(); // array of Business for referencing
// it later
return order.save() //then save the order,
.then(res => Order.findById(res._id) // and populate
.populate('businessReference')
.exec())
}
catch (error) {
throw error;
}
}
Since the exec() will return promise only if it doesn't have any arguments, so I returned it this way. For more info regarding this, please look into
this awesome explained stackoverflow post

Cannot delete json element

I have a node js function:
function func() {
USER.find({},function(err, users){
user = users[0];
console.log(user); // {"name":"mike", "age":15, "job":"engineer"}
user.name = "bob"; //{"name":"bob", "age":15, "job":"engineer"}
delete user.name;
console.log(user); // {"name":"mike", "age":15, "job":"engineer"} name still there??
});
}
Here USER is a mongoose data model and find is to query the mongodb. The callback provide an array of user if not err. The user data model looks like
{"name":"mike", "age":15, "job":"engineer"}.
So the callback is invoked and passed in users, I get the first user and trying to delete the "name" from user. The wired part is I can access the value correctly and modify the value. But if I 'delete user.name', this element is not deleted from json object user. Why is that?
As others have said, this is due to mongoose not giving you a plain object, but something enriched with things like save and modifiedPaths.
If you don't plan to save the user object later, you can also ask for lean document (plain js object, no mongoose stuff):
User.findOne({})
.lean()
.exec(function(err, user) {
delete user.name; // works
});
Alternatively, if you just want to fetch the user and pay it forward without some properties, you can also useselect, and maybe even combine it with lean:
User.findOne({})
.lean()
.select('email firstname')
.exec(function(err, user) {
console.log(user.name); // undefined
});
Not the best workaround, but... have you tried setting to undefined?
user.name = undefined;

Does MongooseJS return a new fresh result object on save()?

By default MongoDB on collection.save() returns a WriteResult object as stated in the documentation:
The save() returns a WriteResult object that contains the status of the insert or update operation.
But with Mongoose (and I guess the underlying mongodb driver in node) you can add a second parameter that is populated with the entire object that you just inserted and with the new _id:
var user = new User(req.body);
user.save(function (err, userResult) {
if (err) {
log.error(err);
}
log.debug('User data: ', userResult);
});
So my question:
Does userResult contain retrieved data from Mongo and it's a fresh object OR is the object passed already passed into the save() method and from the database call is merged with only some partial data like the generated _id and/or created date?
If you take a look at Model.prototype.save():
https://github.com/Automattic/mongoose/blob/8cb0e35/lib/model.js#L254
It looks like you get back the same model instance (self) in your userResult.
To answer your question, the object returned is a new object -- not the original one (so user != userResult)
Mongoose supports a few options -- new: true and lean: false settings may be of interest to you to modify how data is returned in different operations (if you want it to be a new option or do not care).
You can verify this 'new object' is the case by using the following:
var user = new User(req.body);
user.save(function (err, userResult) {
if (err) {
log.error(err);
}
user.name = 'TEST';
console.log(`${userResult.name} will be undefined but ${user.name} will not be`);
log.debug('User data: ', userResult);
});

How to get all count of mongoose model?

How can I know the count of a model that data has been saved? there is a method of Model.count(), but it doesn't seem to work.
var db = mongoose.connect('mongodb://localhost/myApp');
var userSchema = new Schema({name:String,password:String});
userModel =db.model('UserList',userSchema);
var userCount = userModel.count('name');
userCount is an Object, which method called can get a real count?
Thanks
The reason your code doesn't work is because the count function is asynchronous, it doesn't synchronously return a value.
Here's an example of usage:
userModel.count({}, function( err, count){
console.log( "Number of users:", count );
})
The code below works. Note the use of countDocuments.
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/myApp');
var userSchema = new mongoose.Schema({name:String,password:String});
var userModel =db.model('userlists',userSchema);
var anand = new userModel({ name: 'anand', password: 'abcd'});
anand.save(function (err, docs) {
if (err) {
console.log('Error');
} else {
userModel.countDocuments({name: 'anand'}, function(err, c) {
console.log('Count is ' + c);
});
}
});
You should give an object as argument
userModel.countDocuments({name: "sam"});
or
userModel.countDocuments({name: "sam"}).exec(); //if you are using promise
or
userModel.countDocuments({}); // if you want to get all counts irrespective of the fields
For the older versions of mongoose, use
userModel.count({name: "sam"});
The collection.count is deprecated, and will be removed in a future version. Use collection.countDocuments or collection.estimatedDocumentCount instead.
userModel.countDocuments(query).exec((err, count) => {
if (err) {
res.send(err);
return;
}
res.json({ count: count });
});
Background for the solution
As stated in the mongoose documentation and in the answer by Benjamin, the method Model.count() is deprecated. Instead of using count(), the alternatives are the following:
Model.countDocuments(filterObject, callback)
Counts how many documents match the filter in a collection. Passing an empty object {} as filter executes a full collection scan. If the collection is large, the following method might be used.
Model.estimatedDocumentCount()
This model method estimates the number of documents in the MongoDB collection. This method is faster than the previous countDocuments(), because it uses collection metadata instead of going through the entire collection. However, as the method name suggests, and depending on db configuration, the result is an estimate as the metadata might not reflect the actual count of documents in a collection at the method execution moment.
Both methods return a mongoose query object, which can be executed in one of the following two ways. Use .exec() if you want to execute a query at a later time.
The solution
Option 1: Pass a callback function
For example, count all documents in a collection using .countDocuments():
someModel.countDocuments({}, function(err, docCount) {
if (err) { return handleError(err) } //handle possible errors
console.log(docCount)
//and do some other fancy stuff
})
Or, count all documents in a collection having a certain name using .countDocuments():
someModel.countDocuments({ name: 'Snow' }, function(err, docCount) {
//see other example
}
Option 2: Use .then()
A mongoose query has .then() so it’s “thenable”. This is for a convenience and query itself is not a promise.
For example, count all documents in a collection using .estimatedDocumentCount():
someModel
.estimatedDocumentCount()
.then(docCount => {
console.log(docCount)
//and do one super neat trick
})
.catch(err => {
//handle possible errors
})
Option 3: Use async/await
When using async/await approach, the recommended way is to use it with .exec() as it provides better stack traces.
const docCount = await someModel.countDocuments({}).exec();
Learning by stackoverflowing,
Using mongoose.js you can count documents,
count all
const count = await Schema.countDocuments();
count specific
const count = await Schema.countDocuments({ key: value });
The highest voted answers here are perfectly fine I just want to add up the use of await so that the functionality asked for can be achieved:
const documentCount = await userModel.count({});
console.log( "Number of users:", documentCount );
It's recommended to use countDocuments() over 'count()' as it will be deprecated going on. So, for now, the perfect code would be:
const documentCount = await userModel.countDocuments({});
console.log( "Number of users:", documentCount );
Model.count() method is deprecated in mongoose version 6.2.0. If you want to count the number of documents in a collection, e.g. count({}), use the estimatedDocumentCount() function instead. Otherwise, use the countDocuments() function instead.
Model.estimatedDocumentCount() Estimates the number of documents in the MongoDB collection. It is Faster than using countDocuments() for large collections because estimatedDocumentCount() uses collection metadata rather than scanning the entire collection.
Example:
const numAdventures = await Adventure.estimatedDocumentCount();
reference : https://mongoosejs.com/docs/api.html#model_Model.estimatedDocumentCount
As said before, your code will not work the way it is. A solution to that would be using a callback function, but if you think it would carry you to a 'Callback hell', you can search for "Promisses".
A possible solution using a callback function:
//DECLARE numberofDocs OUT OF FUNCTIONS
var numberofDocs;
userModel.count({}, setNumberofDocuments); //this search all DOcuments in a Collection
if you want to search the number of documents based on a query, you can do this:
userModel.count({yourQueryGoesHere}, setNumberofDocuments);
setNumberofDocuments is a separeted function :
var setNumberofDocuments = function(err, count){
if(err) return handleError(err);
numberofDocs = count;
};
Now you can get the number of Documents anywhere with a getFunction:
function getNumberofDocs(){
return numberofDocs;
}
var number = getNumberofDocs();
In addition , you use this asynchronous function inside a synchronous one by using a callback, example:
function calculateNumberOfDoc(someParameter, setNumberofDocuments){
userModel.count({}, setNumberofDocuments); //this search all DOcuments in a Collection
setNumberofDocuments(true);
}

Resources