Mongoose Aggregate: Empty results never trigger the callback - mongodb

I am working with mongoose and defined some aggregations in the mongoshell.
Sometimes the match criteria filters out all data. Nevertheless: Mongoose does not call a callback.
This was very easy reproducable,e.g.
Contract.aggregate( {
$match:{user:'dummydata'}},
function (err, result) {
console.log('this never happens');
});
If I put in a existing user id, the callback gets called.
If I use a non existend user id, the callback just is never called.
But how should I know what happend?

Just by accident I found out, that there is the need for a second "no results" callback.
So the right solution is:
Contract.aggregate( {
$match:{user:'dummydata'}
},
function (err, result) {
console.log('this never happens');
},
function(err,result) {
console.log('Crap there is no result');
});

Related

How do you delete a certain id from a document in mongoose? Edit: Why didn't this work (see edit)

So I have this kind-a-like schema at the moment
user:{ _id: string,
shifts:[_id:string],
name: ... ,
...
}
And now I want to delete a shift._id from all my users who have this.
I allready have an array of all the users their id's who have this shift._id.
I've tried this, with shift_id as the id of the shift i want to delete:
userIdArray.forEach(user_id => {
UserSchema.update({_id: user_id}, {$pull: {shifts: shift_id} });
});
and got the error:
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:535:11)
Can somebody explain me what I did wrong?
Edit:
So what i did was, i called a function named:
function deleteShiftIdInUsers(users, shift_id){
users.forEach(user_id => {
UserSchema.update({_id: user_id}, {$pull: {shifts: shift_id} });
});}
and called this function in my async (req, res, next) route.
Now i just execute this code within the async function instead doing it like code...;
deleteShiftIdInUsers(users, shift_id);
res.status(200).json(...);
still new to js, so what did i do wrong?
I think, You need to call the mongoose function with async/await, you should use updateMany with $in instead of looping for user_id
var ObjectId = require("mongoose").Types.ObjectId
async function deleteShiftIdInUsers(users, shift_id){
users = users.map(user_id => ObjectId(user_id))
return await UserSchema.updateMany({"_id": {"$in": users}}, {"$pull": {"shifts": ObjectId(shift_id)} });
}
when you are calling this function
await deleteShiftIdInUsers(users, shift_id);
res.status(200).json(...);

Why does MongoDB not update unless I call ".then res.json(...)" after findOneAndUpdate?

understanding questions here. I am doing a MERN setup website, and I was updating a field in my database like this:
router.post("/updateLogApprovementStatus", (req, res) => {
User.findOneAndUpdate(
{ _id: req.body.userId },
{ $set: { log: req.body.newData } }
).then(user => res.json(user.log));
});
But after doing repeated calls to the api with correct data, my data field wasn't updating. However, when I do this:
router.post("/updateLogApprovementStatus", (req, res) => {
User.findOneAndUpdate(
{ _id: req.body.userId },
{ $set: { log: req.body.newData } }
).then(user => res.json(user.log));
});
My database updates perfectly fine. All I did was adding a res.json(), which occurs after the update since it is in the "then" statement, which makes me wonder why it wored.
I am pretty sure that all I did was adding the then res.json() statement. Any clarifications on why this made it work?
Reason being : "The query executes if callback is passed else a Query object is returned." (bellow the returns section)
.then() isn't really a promise, it's disguised by mongoose but acts as an execution.
You can see it execute queries here
.exec() from the documentation "Executes the query" and returns a Promise (true one)
Mongoose queries are not promises. They have a .then() function for co
and async/await as a convenience. If you need a fully-fledged promise,
use the .exec() function.
If I understand your issue correctly, you should go with something like this:
User.findOneAndUpdate(
{ _id: req.body.userId },
{ $set: { log: req.body.newData } }
).exec();
The exec function will run the query and this way you don't have to handle the promise result.

Passing multiple different Mongodb queries to EJS?

When a user submits a form on my site, I want to show them three items: a breakfast item, a lunch item, and a dinner item. To do this, I thought I'd have to individually do a db.collection("recipes").findOne, then return the result of that code to a variable I could then pass to EJS using res.render("meal-plan", {breakfast:breakfast});
However, it turns out the variables gathered from the findOne query must be passed to EJS before the findOne query is closed or else you can't access them. Now I'm stuck with something like this:
var breakfast;
MongoClient.connect('mongodb://localhost', function (err, client) {
if (err) throw err;
var db = client.db('food_app');
db.collection("recipes").findOne({ "breakfast" : true}, function(err, result) {
if (err) {
console.log(err);
} else {
console.log(result.title);
breakfast = result;
client.close();
}
res.render("meal-plan.ejs", {breakfast:breakfast});
});
});
This successfully allows me to pass the breakfast variable to EJS. However, I want to have multiple variables to pass (breakfast, lunch, dinner). How can I do this if I can only pass one variable? Is it acceptable to use multiple res.renders in multiple queries so I can copy/paste the MongoClient code three times or put it in a function?
In meal-plan.ejs, you should use forEach
See more at https://ejs.co
<%breakfast.forEach(bf){%>
<p><%bf.name%></p>
<%}%>

Meteor: Increment DB value server side when client views page

I'm trying to do something seemingly simple, update a views counter in MongoDB every time the value is fetched.
For example I've tried it with this method.
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
if (Meteor.isServer) {
var message = Messages.findOne(
{_id: messageId}
);
var views = message.views;
// Increment views value
Messages.update(
messageId,
{ $set: { views: views++ }}
);
}
return Messages.findOne(
{_id: messageId}
);
},
});
But I can't get it to work the way I intend. For example the if(Meteor.isServer) code is useless because it's not actually executed on the server.
Also the value doesn't seem to be available after findOne is called, so it's likely async but findOne has no callback feature.
I don't want clients to control this part, which is why I'm trying to do it server side, but it needs to execute everytime the client fetches the value. Which sounds hard since the client has subscribed to the data already.
Edit: This is the updated method after reading the answers here.
'messages.get'(messageId) {
check(messageId, String);
Messages.update(
messageId,
{ $inc: { views: 1 }}
);
return Messages.findOne(
{_id: messageId}
);
},
For example the if(Meteor.isServer) code is useless because it's not
actually executed on the server.
Meteor methods are always executed on the server. You can call them from the client (with callback) but the execution happens server side.
Also the value doesn't seem to be available after findOne is called,
so it's likely async but findOne has no callback feature.
You don't need to call it twice. See the code below:
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
var message = Messages.findOne({_id:messageId});
if (message) {
// Increment views value on current doc
message.views++;
// Update by current doc
Messages.update(messageId,{ $set: { views: message.views }});
}
// return current doc or null if not found
return message;
},
});
You can call that by your client like:
Meteor.call('messages.get', 'myMessageId01234', function(err, res) {
if (err || !res) {
// handle err, if res is empty, there is no message found
}
console.log(res); // your message
});
Two additions here:
You may split messages and views into separate collections for sake of scalability and encapsulation of data. If your publication method does not restrict to public fields, then the client, who asks for messages also receives the view count. This may work for now but may violate on a larger scale some (future upcoming) access rules.
views++ means:
Use the current value of views, i.e. build the modifier with the current (unmodified) value.
Increment the value of views, which is no longer useful in your case because you do not use that variable for anything else.
Avoid these increment operator if you are not clear how they exactly work.
Why not just using a mongo $inc operator that could avoid having to retrieve the previous value?

WaterlineJs find() with no criteria and fields/select provided does not work

I am trying to fetch all the records but with selected fields, I have tried the following ways but none works:
Post.find(
{
where: {},
select: ['title']
}
);
Post.find(
{},
{
fields: {
title: 1
}
}
);
As this answer points out, the fields param "WILL work as long as you pass other params with it such as limit or order."
Alternatively, if you want this throughout your application, you could define a custom toJSON function for your model, under attributes. If not, you could still define it under some other (e.g. filter) and use map to return the custom objects instead of the default model. Remember to take care of the control flow while using map though. Use async/promises/raw logic to avoid returning before all objects are processed.
The issue has been resolved in sails-mongo latest version:
https://github.com/balderdashy/waterline/issues/1098
Thanks
I've played with trying to get above answer to use limit or order to kick in the projection to no avail.
I did see this in the docs located here:
http://sailsjs.org/documentation/reference/waterline-orm/models/native
With an out of the box solution for exactly what you're doing (pasted here for ease of use).
Pet.native(function(err, collection) {
if (err) return res.serverError(err);
collection.find({}, {
name: true
}).toArray(function (err, results) {
if (err) return res.serverError(err);
return res.ok(results);
});
});
Swap out the response base things and change Pet to Post and, this ought to work in the sails console:
Post.native(function(err, collection) {
if (err) throw new Error(err);
collection.find({}, {
title: true
}).toArray(function (err, results) {
if (err) throw new Error(err);
console.log(results);
});
});
You'll still get the _id field, and if you don't want that then hit the Mongo docs on not getting those hint(title: true, _id: false)hint
Hope this helps!