MongoDb query based on whether a field is specified or not - node.js

Apologies if incorrect wording. I have a question regarding MongoDb query. Say I want to get a list of books with specified isbn if passed. If not passed, I would still want the list based on other parameters. Below is my code snippet
async findBooks(rawNameValues) {
try {
const nameValues = this._validate('findBooks', rawNameValues);
//#TODO
console.log('find books', nameValues);
const authorsTitle = new RegExp(`.*${nameValues.authorsTitleSearch}.*`);
console.log('Authors Title', authorsTitle);
const cursor = await this.db.collection(COLLECTIONS.BOOK_CATALOG).find(
{
isbn: '' || nameValues.isbn.toString(),
title: {$regex: authorsTitle}
}
).toArray();
return cursor || [];
} catch (err) {
console.log('Caught Error', err);
}}
However it get caught if no isbn is passed. Any help would be appreciated.

try this before filter option for existance of isbn:
let custom_filter = nameValues.isbn ? {isbn:
nameValues.isbn.toString(), title: {$regex: authorsTitle}} :
{title: {$regex: authorsTitle}};
let query = await
this.db.collection(COLLECTIONS.BOOK_CATALOG).find(custom_filter)

Related

Can't push object into the array after running mongodb query

I was trying to set up the logic for adding some items into an array, which id's express server receives from a client. My program receives the id of the product and then I was fetching the product details from MongoDB query findOne, and then with some customized details I used to push that item into an array but it's not working, whenever I try to push any element after MongoDB query it's not working, I don't know why, but please help me, Sorry for my bad English !
It's an ExpressJS server using MongoDB
Items received: (But it is actually received from the client in the form of JSON) :
const items= [
{
productId:"61e01e7e24612b56c33b06c3",
quantity:"4"
},
{
productId:"61e01e9024612b56c33b06c6",
quantity:"10"
}
]
The actual code here is the problem
let itemsData = [];
items.forEach(async (item) => {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
// If found add that in object
itemsData.push({
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
});
});
The code above doesn't push that object into the itemsData
array
findProduct Function
// Service to find Product
async findProduct(filter) {
return await ProductModel.findOne(filter);
}
If I used that push method and tried only to itemsData.push("hello"); before the MongoDB query it works, but if I put it after the findProduct Query it doesn't work! I don't know what is wrong with it! Somebody help me!
I just want to push those items with detail into itemData object happily which is not happening I tried to console.log(itemsData) it just return [], what should I do?
Try using For Of instead of forEach (don't forget to add async)
let itemsData = [];
for (const item of items) {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
// If found add that in object
itemsData.push({
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
});
}
It's because forEach function is not designed to work well with async calls.
You could use map instead.
This should work:
let itemsData = [];
const promises = items.map(async (item) => {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
return {
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
};
});
itemsData = await Promise.all(promises);
When you use map with async, you will have an array of promises, so you can use Promise.all to wait for the values to get resolved.
Check this out for more details.

Nested documents in mongodb implementation, just like Reddit comments

In my project, I would like to implement a comment section which consists of list of comments.
const myschema= mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
//other fields
comments : [comment]
},{collection : 'TABLE_NAME'} );
const comment= mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
commentid: String, // id consists of uuid1()
senderid: Number, //user's id
body: String, // useful information
parent: String,// parent id consists of uuid1()
children: [] // I couldn't add children = [comment], it gives me an error,
//instead, I make it empty and will fill it when a comment comes
})
In the request tab, I will receive a JSON in the following format:
{
"userid": "NUMBERID HERE",
"comment": "COMMENT BODY",
"parent" : "Comment's parent id"
}
I would like to add a comment which can be a child of another comment. How can I search and find the appropriate position?
If there are no parent in JSON body, I'm doing this:
// import comment somewhere at the beginning
.then(doc =>{
var newc= new comment();
newc.cid = uuidv1();
newc.sender = req.body.userid;
newc.body = req.body.comment;
newc.parent = "";
newc.children = "";
doc.comments.push(newc);
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
res.status(500).json({error: err});
})
}
I have no idea how to find a comment that resides in a deep level
You cannot search for an array element or object property given an unspecified, arbitrarily-nested depth. It just isn't possible. You would need to instead handle this in the application layer. Denormalizing your data is fine in many cases, but arbitrary nesting depths isn't a recommended use case for data denormalization, especially since you can't index efficiently!
If you want a pure MongoDB solution, then you'll need a different document structure. I would recommend taking a look at the documentation, particularly the section concerning an array of ancestors, in order to properly model your data.
I have found a solution.
The manually traversing the comments and inserting in the right place works. However, it only works up to some level. In my case, I can insert a comment below comment and save it. I can also insert a 3rd deep level comment and see the JSON dump of the object that I have inserted or even wrap it in an HTTP response object and send to the client. But the database does not save this update.
What I did is, after inserting the comment in the correct place I added this code before saving to the database.
doc.markModified('comments'); // this is added
doc.save().then(/*prepare response*/);
Somehow the MongoDB or mongoose or javascript interpreter knows the document has been changed and save the updated version. If the markmodified part is not specified, probably the compiler thinks that the object has not been modified and skips it.
Hopefully, this helps people who encounter this issue.
This is my implementation
// find the document which comment is going to be inserted
MyDOC.findOne({'id' : req.params.id})
.exec()
.then(doc =>{
// if the comment has a parent, it should be inserted in nested
if(req.body.parent != null && req.body.parent != undefined)
{
// find correct position and insert
findComment(doc.comments, req.body);
doc.markModified('comments');
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
else
{
//Add comment to the root level
var comment= new Comment();
comment.cid = uuidv1();
comment.body = req.body.comment;
comment.parent = "";
doc.comments.push(comment);
doc.markModified('comments');
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
})
function findComment(comment, body)
{
comment.forEach(element => {
if(element.children.length != 0)
{
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
if(findComment(element.children, body, usr))
return true;
}
else
{
// this comment does not have children. If this comments id and the body's parent is equal, add it
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
return false;
}
});
}

one query inside another MongoDB hangs

I am trying to get only get Notes [from the notes collection] that come from meetings [from the meetings collection] that don't contain the word 'test' in them:
function getNotes(done) {
noteSchema.find({}).exec((err, notes) => {
var numNotes = 0;
async.each(notes, (n, next) => {
userSchema.findById(n.userId, (err, user) => {
if (err || !user) { next(); return; }
var emailsStr = utils.getEmailsString(user.emails);
if (!utils.toSkipEmail(emailsStr)) {
meetingSchema.findById(n.meetingId, (err, meeting) => {
if (err || !meeting) { next(); return; }
if (meeting.name.displayValue.indexOf('test', 'Test') == -1) {
numNotes++;
}
next();
});
}
})
}, (err, result) => {
console.log(util.format('Total Number of Notes: %d', numNotes));
done(null);
});
});
}
The code works fine without adding in the lines to find the meetings by ID. It hangs at that point.
For reference, here is the start of a function that comes later to filter out any 'test' or 'Test' containing meetings.
function getMeetings(done) {
meetingSchema.find({
'name.displayValue': { '$regex' : '(?!.*test)^.*$' , '$options' : 'i' }
}).exec((err, meetings) => {
Relevant lines of Notes Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var noteSchema = mongoose.Schema({
meetingId: {type: String, default: ''},
});
exports.Note = mongoose.model('Note', noteSchema);
The meeting schema has no notes field.
So, if I were going to go after a solution like this, where I wanted to get all notes that weren't part of a meeting w/ the word 'test' in the name, I'd probably go the other way with it, unless there's a whole ton of notes you could just get all notes and populate them with their meetings, and then do the filtering. assuming your NoteSchema defines something like:
meeting : { type: ObjectId, ref: 'Meeting'}
then in your query you could do (given the notATest function that returns true or false appropriately:
Note.find({}).populate('meeting').exec((e, n) => {
_.omit(n, (note) => { return notATest(note.meeting.name); });
});
Alternatively, you could search for all meetings that are not a test first, and call .populate('notes') on them, if the 'ref' goes the other way.

Mongoose text search pagination in MongoDB/Node.js

I have this problem , i don't know how to make a pagination in Mongoose text search module, i am totally beginner, so please help, this is my code:
searchModul.create({ title: title, description: description }, function (err) {
if (err) return handleError(err);
searchModul.textSearch(title, function (err, output) {
if (err) return handleError(err);
res.render('search', {
title : 'title',
results : output.results
});
});
});
and also i want to know how to display that pagination in the search.ejs view. and thanks in advance.
To implement pagination, use $limit to define a limit for each query, and $skip to navigate pages.
From the docs:
$limit takes a positive integer that specifies the maximum number of documents to pass along.
$skip takes a positive integer that specifies the maximum number of documents to skip.
There are previous questions like this one, check out the answers here and a more detailed tutorial here.
Hope that helps!
I think you want something like this:
searchModul.find({ $text: { $search: title }}).skip(50).limit(50).exec(function (err, output) {
if (err) return handleError(err);
res.render('search', {
title : 'title',
results : output.results
});
});
This will return the second 50 items that match. .skip(50) the number of items skip and .limit(50) is the number of items to return.
const field = req.query.field;
const search = {};
search[field] = req.query.searchValue;
search["role"] = req.params._role;
search["status"] = parseInt(req.query.status);
user = await Users.paginate(search, {
limit: parseInt(req.query.limit),
page: parseInt(req.query.page),
});
OR
var ObjectId = mongoose.Types.ObjectId;
let _id = new ObjectId(req.query.q);;
user = await Users.paginate(
{
role: req.params._role,
_id: _id,
},
{
limit: parseInt(req.query.limit),
page: parseInt(req.query.page),
});

How to update document in mongo with nested schema

I have the following code:
add_new_patient : function(username,pname,pid,pdesc,callback){
var find_md = function(){
return function(err,pat){
if (err){
console.log('Error at adding patient: error at searching for users');
callback(2);//In the callback method I'm passing I have specific behavior for error handling for error codes.
return;
}
if (pat.lenth > 0){
console.log('searching for md');
MD.find({'mdname':username},add_patient(pat));
}else{
console.log('Error at adding patient: no user found');
callback(-1);
return;
}
}}
var add_patient = function(pat){
return function(err,md){
if (err){
console.log('Error at adding patient: cannot find md');
callback(-1);
return;
}
callback(0);
}
}
console.log('searching for user '+pid);
User.find({'name':pid},find_md());
}
And these are my schemas:
var mdSchema = mongoose.Schema({
mdname : String,
pacients : [userSchema.ObjectId]
});
var userSchema =mongoose.Schema({
name : String,
password : String,
phone : String,
history : [{timestamp: Date , heart: Number }],
md : {mdname: String, contact: Number}
});
As you can guess from the code I want to add patients to the dms. First I search for the pid in the database. If I find a patient I start to look for mds. When I find the md I want to add the patient to the md. Now I don't know how to add them. The schema shows that I have an array of schemaUser, which is the type of patient, but I don't know how to append to it, not how to create an MD model from object from the data I received from the query. Also what should I insert into the array of patients? The _id of the found patient or the whole object?
I managed to solve it in the following way:
var add_patient = function(pat){
return function(err,md){
if (err){
console.log('Error at adding patient: cannot find md');
callback(-1);
return;
}
var query = {mdname: md.mdname};
console.log(md);
var doThis = { $addToSet: { patients: pat._id } };
console.log(query);
MD.update(query,doThis,done_adding());
}
}
var done_adding = function(){
return function(err,dat){
if (err){
console.log('Error at the end of adding new patient!');
callback(-1);
return;
}
console.log('new patient added');
callback(0);
}
So what this does is: when I have the md to whom I want to add a patient/user I use the update method, with the $addToSet operation so I will have a set of patients associated with an md. I don't know why, but the same code did not work for me with the $push parameter. Then simply nothing happened and when I set the upsert option to true my whole record in the database was overwritten by the id.

Resources