I have created a Background Job in Parse Cloud Code that sends out email notifications based on a date in one of my Parse classes.
Here is the idea: Query the class that contains the date. Iterate over each object returned and check the date field. If the date is equal to today, send out an email notification, change the date to null and save it back to Parse.
However, it seems that not all the objects are saved back to Parse. I suspect this an issue with my promise chains, but I am having a hard time diagnosing the exact issue or how to fix it. Below is relevant code
Parse.Cloud.job("job", function(request, status) {
// Query for all users
var query = new Parse.Query(className);
query.each(function(object) {
if (condition) {
object.set(key, false);
object.save(null, {
success:function(object){
// This never executes!
},
error: function(error){
}
}).then(function(){
// This never executes
console.log("Save objects successful");
},
function(error){
status.error("Uh oh, something went wrong saving object");
});
// Set hiatus end successful
Mailgun.sendEmail({
});
}
});
});
This line console.log("Save objects successful"); in the objects.save() promise chain does not ever get executed - even when the subscription object is successfully saved to Parse.
Additionally, if there are more than 5 objects returned by the query, only the first 5 are successfully saved back to Parse. Any additional saves are not executed and email notifications are not sent.
I'd clean it up as follows, relying on Promise.when ...
var savePromises = []; // this will collect save promises
var emailPromises = []; // this will collect email promises
// your code to setup the query here
// notice that this uses find() here, not each()
query.find(function(subscriptions) {
_.each(subscriptions, function(subscription) { // or a for loop, if you don't use underscore
// modify each subscription, then
savePromises.push(subscription.save());
// prepare each email then
var emailPromise = Mailgun.sendEmail({ /* your email params object here */ });
emailPromises.push(emailPromise);
});
// now do the saves
return Parse.Promise.when(savePromises);
}).then(function() {
// now do the emails
return Parse.Promise.when(emailPromises);
}).then(function() {
// Set the job's success status
status.success("Subscriptions successfully fetched");
// and so on with your code
You might also consider combining the saves and the emails into one big array of promises, but it might be better to do it in two batches serially since they have different failure modes.
Related
I'm currently running the following Parse cloud code job which iterates through every user in the database, and I'm starting to hit the 15 minute time limit. How can I set this to run in batches of 500 users at a time instead of all users at once? I would need it to run through the users in order, so user 1-500 for the first batch, then 500-1000 for the 2nd batch, and so on, that way it doesn't repeat anyone.
Parse.Cloud.job("MCBackground", function(request, status) {
// ... other code to setup usersQuery ...
Parse.Cloud.useMasterKey();
var usersQuery = new Parse.Query(Parse.User);
return usersQuery.each(function(user) {
return processUser(user)
.then(function(eBayResults) {
return mcComparison(user, eBayResults);
});
})
.then(function() {
// Set the job's success status
status.success("MCBackground completed successfully.");
}, function(error) {
// Set the job's error status
status.error("Got an error " + JSON.stringify(error));
});
});
Well, for starters, from your comment I see you have a constraint you can add: only query for user objects that has a matchCenterItem:
query.exist("matchCenterItem");
I don't know how many percent of your users would not have this field, but maybe you already now will have reduced the number of Users to fetch.
I have no idea how often this job is run or what you are comparing with, but how likely is it that this data will change? Do you always need to run it on ALL users, or can you set a flag so that you don't run it on the same user again? At all, or until some time has passed?
I have a web app, that manages a budget for a user.
In the settings page, I can edit the budget, after clicking "save" I return to the main page, and there I have line that states the budget amount.
The problem is, that when I log in, I see the correct budget, after editing the budget and returning to the main page, I still see the old amount. Only after logging out and re-login again, that line in the main page updates to new amount.
Any solutions?
The code that saves the new budget:
$("#saveNewBudgetAmount").click(function(){
var User = Parse.User.extend("User");
var query = new Parse.Query(User);
var newBudget = $("#newBudgetSum").val();
query.equalTo("objectId", Parse.User.current().id);
query.first({
success: function (User) {
User.save(null, {
success: function (user) {
User.set("budget", newBudget);
User.save();
location ="Mainpage.html";
}
});
}
});
});
and the code that displays it on the main page:
var MBudget = (function () {
if (Parse.User.current()) {
return("Your monthly budget is:" +" "+Parse.User.current().get("budget")+" "+"<a href=Settings.html>(Edit)</a>");
}
A few things are happening.
First you should simplify your code, and use both alerts AND error handling so that you know if your code works, and when callbacks are made. You are also calling .save() once before any new values are set, so you have a useless save.
You also need to have a success and error callback for EVERY save function you use - .save() by itself is an asynchronous method, and since you are not calling a success function within your save method, so your app will navigate back to "Mainpage.html" before it is known whether or not the save function worked. Here is a much better implementation:
var newBudget = $("#newBudgetSum").val();
var currentUser = Parse.User.current();
currentUser.save(
{
// Set as many properties as you like in this field,
// think of it as a JSON object except you don't
// have to enclose the values in strings.
budget : newBudget,
}, {
success: function(user) {
alert("Budget successfully saved, new budget is: " + user.get("budget"));
},
error: function(error) {
// error functions will always have an error argument handed back to the client,
// with properties error.code and error.message. Error messages are incredibly useful.
alert("Budget save failed, error: " + error.code + " " + error.message);
}
});
Another tip is that I recommend all users of Parse.com to use alert() messages for their success and error callbacks while in development, for many reasons - but the key reasons are 1) it will alert you to whether or not the code worked, and 2) it will prevent accidental bugs from causing infinite requests to the Parse.com server, which does happen sometimes, and will cause them to charge your account.
The problem is this api : Parse.User.current() never sync data in the cloud. The data of Parse.User.current() is derived from localstorage. You have to refresh it manually by calling save or fetch method on it.
Parse.User.Current() return normal Parse.User object. You can use it directly without querying in advance. So you can just rewrite your first codes as following :
$("#saveNewBudgetAmount").click(function() {
var user = Parse.User.current() ;
var newBudget = $("#newBudgetSum").val();
user.set("budget", newBudget);
user.save(null,{
success: function(user) {
// feedback user and redirect page.
},
error: function(user, error) {
//You should always handle error.
console.error(error.message) ;
}
}) ;
});
With this code, the local data would refresh when ths save() call done successfully. On your main page the budget value of Parse.User.current() object would be correct.
Is it possible to setup a web hook for anytime a push notification is sent via Parse?
I want to retrieve the details of the push notification and set a column against any devices the push notification was sent to
No hook that I'm aware of, but you could run all of your pushes through one place in your code (either client or a cloud function), and do whatever post-push work you want to do there. Presuming JS and advanced targeting, it could look like this:
function pushToInstallations(query, data) {
var params = { where: query, data: data};
return Parse.Push.send(params).then(function() {
// this is the interesting part, run the installation query
return query.find();
}).then(function(installations) {
var date = new Date();
// presumes underscore, but a regular for loop works too
_.each(installations, function(installation) {
installation.set("mostRecentPushDate", date);
});
return Parse.Object.saveAll(installations);
});
}
Then, wherever in your code you were building an installation query and calling push, call your new pushing function instead, like this:
var query = new Parse.Query(Parse.Installation);
query.equalTo('someColumn', someValue);
pushToInstallations(query, {alert: "some message"}).then(function(installations) {
// these installations passed back were pushed to and updated
}, function(error) {
// handle error
});
Scenario = I am slowly but surely wrapping my head around what is going on with Parse's cloud code features. I just need some help from those who would like to answer some short, relatively simple questions about what is going on in some sample cloud code functions.
The code I will use in this example is below
1) cloud code
Parse.Cloud.define('editUser', function(request, response) {
var userId = request.params.userId,
newColText = request.params.newColText;
var User = Parse.Object.extend('_User'),
user = new User({ objectId: userId });
user.set('new_col', newColText);
Parse.Cloud.useMasterKey();
user.save().then(function(user) {
response.success(user);
}, function(error) {
response.error(error)
});
});
2) called from iOS
[PFCloud callFunction:#"editUser" withParameters:#{
#"userId": #"someuseridhere",
#"newColText": #"new text!"
}];
This code was taken from here
Question 1 =
(request, response)
I am confused by what this is. Is this like typecasting in iOS where I am saying (in the iOS call) I want to pass an NSString into this function ("userId") and inside the cloud code function I'm going to call it "request"? Is that what's going on here?
Question 2 =
Parse.Object.extend('_User')
Is this grabbing the "User" class from the Parse database so that a "PFObject" of sorts can update it by creating a new "user" in the line below it?
Is this like a...
PFObject *userObject = [PFObject objectWithClassName:#"User"]?
Question 3 =
user.set('new_col', newColText)
This obviously 'sets' the values to be saved to the PFUser (~I think). I know that the "newColText" variable is the text that is to be set - but what is 'new_col'? Only thing I can think of is that this sets the name of a new column in the database of whatever type is being passed through the "request"?
Is this like a...
[[PFUser currentUser] setObject: forKey:]
Question 4 =
Parse.Cloud.useMasterKey()
Without getting too technical, is this basically all I have to type before I can edit a "User" object from another User?
Question 5 =
user.save().then(function(user) {
response.success(user);
}
Is this like a...
[user saveInBackgroundWithBlock:]?
and if so, is
function(error) {
response.error(error)
just setting what happens if there is an error in the saveInBackgroundWithBlock?
Please keep in mind, I know iOS - not JavaScript. So try to be as descriptive as possible to someone who understands the Apple realm.
Here's my take on your questions:
The request parameter is for you to access everything that is part of the request/call to your cloud function, it includes the parameters passed (request.params), the User that is authenticated on the client (request.user) and some other things you can learn about in the documentation. The response is for you to send information back to the calling code, you generally call response.success() or response.error() with an optional string/object/etc that gets included in the response, again documentation here.
That's a way of creating an instance of a User, which because it is a special internal class is named _User instead, same with _Role and _Installation. It is creating an instance of the user with an ID, not creating a new one (which wouldn't have an ID until saved). When you create an object this way you can "patch" it by just changing the properties you want updated.
Again, look at the documentation or an example, the first parameter is the column name (it will be created if it doesn't exist), the second value is what you want that column set to.
You have to do Parse.Cloud.useMasterKey() when you need to do something that the user logged into the client doesn't have permission to do. It means "ignore all security, I know what I'm doing".
You're seeing a promise chain, each step in the chain allows you to pass in a "success" handler and an optional "error" handler. There is some great documentation. It is super handy when you want to do a couple of things in order, e.g.
Sample code:
var post = new Parse.Object('Post');
var comment = new Parse.Object('Comment');
// assume we set a bunch of properties on the post and comment here
post.save().then(function() {
// we know the post is saved, so now we can reference it from our comment
comment.set('post', post);
// return the comment save promise, so we can keep chaining
return comment.save();
}).then(function() {
// success!
response.success();
}, function(error) {
// uh oh!
// this catches errors anywhere in the chain
response.error(error);
});
I'm pretty much at the same place as you are, but here are my thoughts:
No, these are the parameters received by the function. When something calls the editUser cloud function, you'll have those two objects to use: request & response. The request is basically what the iOS device sent to the server, and response is what the server will send to the iOS device.
Not quite that. It's like creating a subclass of _User.
Think of Parse objects types as a database table and it's instances as rows. The set will set (derp) the value of 'newColText' to the attribute/column 'new_col'.
Not sure, never used that function as I don't handle User objects. But might be that.
Pretty much that. But it's more sort of like (pseudo-code, mixing JS with Obj-C):
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error){
if(error){
response.error(error); // mark the function as failed and return the error object to the iOS device
}
else{
response.success(user); // mark the function call as successful and return the user object to the iOS device
}
}];
How would I check to see if a given PFUser is friends with another user given a PFUser and an array of other PFUsers in the cloud?
Something like:
Parse.Cloud.beforeSave("Message", function(request, response) {
if (request.object.get("creator") != NULL) {
var user = request.object.get("creator");
var error = false;
for (somepfuser in request.object.get("recipients")
if (user and somepfuser are NOT in friends table)
error = true;
if (error)
response.error("you must be friends to send message");
else
response.success();
});
I suggest you to make a promise series where each promise check the relationship between the users pair in the friends table. Moreover, you should do it in a custom function instead of an event listener (like beforeSave or afterSave ... ) due the Parse restrictions on the time execution limit. For event listener like these you have 3 seconds maximum, in a custom function you'll have up to 7 seconds before parse drop the request.
see promise series reference: https://parse.com/docs/js_guide#promises-series
and promise series example: wait for query to finish before sending response
hope it helps