MeteorJS: Get success callback from user update? - mongodb

So, I just want to know if my user update was successful on client, so I can notify the user that the update worked.
//Client Side
return Meteor.call('handleUpdateUser', _id, _username, function(err, res) {
if (err) {
// also, what is best practices for handling these errors?
}
console.log(res);
});
//Server Side
Meteor.methods({
handleUpdateUser(id, username) {
if (check for user...) {
return if found
}
return Meteor.users.update({_id: id}, {$set: {username: username}},
function(err, count, res) {
if (err) {
console.log(err) // again, best practices for handling these errors?
}
return res;
});
}
I currently get undefined in console on client.
permissions.js:
//Server Side
Meteor.users.allow({
update:function(userId, doc, fields, modifier) {
return userId && doc._id === userId;
}
});
Thoughts?

you're getting undefined because your server side method is async and, absent treating it as such, your client will see the result of the synchronous part. i.e. the implicit undefined returned at the end of handleUserUpdate().
i use a Future to treat it as async. e.g.
const Future = Npm.require('fibers/future');
Meteor.methods({
handleUpdateUser(id, username) {
let future = new Future();
Meteor.users.update({_id: id}, {$set: {username: username}}, function(err, res) {
if (err) {
console.log(err);
future.throw(new Meteor.Error('500', err));
}
future.return(res);
});
return future.wait();
}
});
now, Meteor will wait to notify the client until the future is handled, either through a return or a throw. your client call should work as you expect.

From the docs: https://docs.meteor.com/api/collections.html#Mongo-Collection-update
On the server, if you don’t provide a callback, then update blocks
until the database acknowledges the write, or throws an exception if
something went wrong. If you do provide a callback, update returns
immediately. Once the update completes, the callback is called with a
single error argument in the case of failure, or a second argument
indicating the number of affected documents if the update was
successful.
So if you want to wait for result, you should think of update as of a sync operation to avoid all those Futures:
Meteor.methods({
handleUpdateUser(id, username) {
return Meteor.users.update({ _id: id }, { $set: { username: username } });
}
});
If operation fails, it will throw an error and pass it on to the client. If it succeeds, then it will return the result.
If you want to check what was the error (on server side), then wrap it into a regular try {} catch(e) {}.

Related

Mongoose: Defining 404 status for not finding a document doesnt work

I,m learning MongoDB and mongoose and now I have a problem in defining a 404 status for my route handler. Here is the code:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id
try {
const user = await User.findById(_id)
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (error) {
res.status(500).send()
}
})
Now if I give it an id that doesn't exist, it doesn't give me 404 Not Found status. it only executes the catch block which is not what I want.
I would appreciate it if you tell me where I made mistake or tell me a way to get error handling for that.
Thanks
The problem
As you can see in the log
CastError: Cast to ObjectId failed for value "6082d50a2c89db3164" at path "_id" for model "User"
It means : the value you provide to findById function ("6082d50a2c89db3164") is not a valid ObjectId.Then the catch block is executed.
Suggestion
1. Validate the parameter before query in database
I understand that you're trying to provide some id that doesn't exist in the database to test. But IMHO, there a difference between 2 cases :
you provide a valid id, and this id cannot be found in the database. It should return 404 in this case
you provide an invalid id in the request, it could be a string like "6082d50a2c89db3164", or even "#Q*&$(##*" or anything we could imagine. For this case, it could be better if we validate the input (req.params._id) to ensure that the format is valid. The code will be something like this:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
// validate params
if(!isValidateObjectId(_id)) { // the function we need to write
res.status(200).send("Invalid params"); // you can define your status and message
return;
}
// good params, get user from database
try {
const user = await User.findById(_id)
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (error) {
res.status(500).send()
}
})
2. Use findOne() method instead of findById
If you want a simpler solution, don't use findById because the function expects a valid ObjectId. We can use findOne() method :
app.get('/users/:id', async (req, res) => {
const _id = req.params.id
try {
const user = await User.findOne({_id : _id})
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (error) {
res.status(500).send()
}
})
(IMHO, the first solution is better though..)
Some helpful link :
https://docs.mongodb.com/manual/reference/method/ObjectId/
Can I determine if a string is a MongoDB ObjectID?
https://mongoosejs.com/docs/api.html#model_Model.findOne

I want my Dialogflow bot to say a message which includes the result from a MongoDB query

I am querying a collection in MongoDB from Dialoglow Fulfillment. I then want my bot to respond with a message which includes this query. The code in the function of the Dialogflow Fulfillment is:
function readRecord(agent){
var name;
MongoClient.connect(uri, function(err, client) {
const collection = client.db("test").collection("data");
collection.find({fname: 'Example'}).toArray(function(err, result){
if (err) throw err;
console.log(result);
name = result.lname;
agent.add("Found last name: ", name);
});
client.close();
});
}
When I run this I get no response from my from the bot. When I console.log(result) the information is there but I can't seem to get the bot to say it.
The issue is that the intent handler expects you to return a Promise if you are doing any asynchronous functions - like accessing a database. The easiest way to do this is to change from using callbacks with MongoDB to using versions of the functions that return Promises, and then to return the promise.
I haven't tested, but something like this might work
return MongoClient.connect( uri )
.then( client => {
const collection = client.db("test").collection("data");
return collection.find({fname: 'Example'}).toArray();
})
.then( result => {
let name = result[0].lname;
agent.add("Found last name: "+name);
});

Mongoose - always return null instead of an error when no result found?

Why mongoose always return null instead of an error when no result found?
Person.findOne({ 'name': 'Ghost' }, function (err, person) {
console.log(err); // null
if (err) return handleError(err);
});
The person 'Ghost' does not exist in my db so I am expecting an error but I get null instead. Why? How can get get an error instead?
Because mongoose only return an error when there is an error, not finding any result is not an error. You can handle the response if you got null, like :
Person.findOne({ 'name': 'Ghost' }, function (err, person) {
if(person === null) {
console.log('No results found');
}
if (err) return handleError(err);
});
or
Person.findOne({ 'name': 'Ghost' }, function (err, person) {
if (err) {
return handleError(err);
} else if (person) {
return person;
} else {
return 'No results found';
 }
});
Mongoose use err for internal errors like failing validation etc. If query complete successfully (with results or not) err will contain null.
What documentation says:
Anywhere a callback is passed to a query in Mongoose, the callback
follows the pattern callback(error, results). What results is depends
on the operation: For findOne() it is a potentially-null single
document, find() a list of documents, count() the number of documents,
update() the number of documents affected, etc. The API docs for
Models provide more detail on what is passed to the callbacks.

meteor how to manage async updates in a loop

I have this loop:
properties.forEach(function(property) {
console.log("property: " + property);
var upsertValues = {};
upsertValues["ID"] = property.ID;
Properties.upsert(upsertValues,
{$set: property},
function(err, nbr) {
if(err)
console.log(err);
else
console.log("upsert successful" + nbr);
});
});
setTimeout(function () {
Fiber(function() {
Meteor.call("removeOldProperties", modification_date);
}).run();
}, 30000)
})
Basically, it updates a bench of documents and at the end, it removes all the once who have not been updated.
I had to use a TimeOut because without that, I removes the documents before their update, as all the Meteor.upsert statements are async.
Is there a better way to do it (without having to use this timeout) ?
Thanks,
Couple thoughts:
upserts are fast, no need for a callback
Fiber is for the server
I don't understand how your upsertValues was a valid query. Is this referring to the document _id? If so, convention is to keep using the name _id, if not, I'd use a more descriptive name. Was this code functioning??
What remains:
var upsertsCompleted = 0;
properties.forEach(function(property) {
Meteor.call("upsertProperties", property, function() {
if (++upsertsCompleted === properties.length) {
Meteor.call("removeOldProperties", modification_date);
}
}
Meteor.methods({
upsertProperties: function (property) {
return Properties.upsert(property.ID, {$set: property});
}
});

How to get the callback function return values in NodeJS

Below is the code uses mongoskin for mongodb access with nodejs.
How do i access the callback function return value from the outside?
app.get('/', function(req, res) {
var ret = db.collection('counters').findAndModify(
{_id: 'messagetransaction'},
[],
{$inc : {next: 1}},
true,
true,
function(err, counter) {
if (err) {
throw err;
}else{
console.log(counter.next);
return counter.next;
}
}
);
});
console.log(ret);
I got the error as below,
ReferenceError: ret is not defined
Please help me on this!
The problem is you never know when the callback is going to fire; its asynchronous. Therefore you don't want to have anything wait on the result. What you should do is instead of returning a value, you should invoke a function, passing the value, and that function should do what you want it to do with the result.