how to make two queries atomic in parse.com in beforeSave and afterSave triggers - parse-platform

I made two classes in parse User(by default) and UserData.
BeforeSave Trigger as follows:
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
var userDataObject = new Parse.Object("UserData");
var fromUserPointer = {"__type":"Pointer","className":"_User","objectId":request.object.id};
return userDataObject.save({score: 0, ideasCount: 0, followersCount:0, return:0}).then(function (userData) {
var userDataPointer = {"__type":"Pointer","className":"UserData","objectId":userData.id};
request.object.set("userData", userDataPointer);
response.success();
}, function(error) {
response.error(error.message);
});
});
It saves user's UserData and takes its UserData pointer field and saves in UserData class.
Parse.Cloud.afterSave(Parse.User, function(request) {
Parse.Cloud.useMasterKey();
var userPointer = {"__type":"Pointer","className":"_User","objectId":request.object.id};
var userData = request.object.get("userData");
if (userData) {
var userDataPointer = {"__type":"Pointer","className":"UserData","objectId":userData.id};
var userDataQuery = new Parse.Query("UserData");
userData.set("user", userPointer);
return userData.save().then (function (userData) {
var activityObject = new Parse.Object("Activity");
return activityObject.save({fromUserData: userDataPointer, from:userPointer,
toUserData:userDataPointer, to:userPointer, type:"follow"});
}).then (function (success) {
}, function (error) {
console.error("Error in afterSave(user) : " + request.object.id + ":" + error.message);
});
}
});
The problem is that, before saving in the User class, it sometimes does not create UserData, which it should create. Hence these two triggers must run atomically, which they do not.

Related

Object from Pointer in Parse Cloud Code

I'm attempting to create my first Parse Cloud Code function and am running into an issue:
Parse.Cloud.afterSave("Message", function(request) {
var fromUser = request.object.get("fromUser");
var toUser = request.object.get("toUser");
console.log(fromUser); // user pointer
console.log(toUser); // user pointer
});
As you can see both fromUser and toUser is a pointer when what I actually want is the user objects themselves. What is the best way to do this?
You can create a new query to get user informations.
var query = new Parse.Query(Parse.User);
query.get(request.object.get('fromUser').id, {
success: function(user) {
// What you want with user informations
},
error: function() {}
});
You can try this, but I've never try.
var query = new Parse.Query(Parse.User);
query.equalTo('objectId', request.object.get('fromUser').id);
query.equalTo('objectId', request.object.get('toUser').id);
query.find({
success: function(users) {
// What you want with users information
},
error: function() {}
});
I am too late, but I hope this will work
you can use
Parse.Cloud.beforeSave("Message", function(request, response) { ....
or
Parse.Cloud.afterSave("Message", function(request) { ....
.
this is how to use beforeSave
Parse.Cloud.beforeSave("Message", function(request, response) {
var message = request.object;
var fromUser = message.get("fromUser"); // you must have this User object, if it's null, then the object is null in the table
var toUser = message.get("toUser");
// fromUser and toUser columns must be Pointer<User> and have values
}).catch(function(error) {
response.error("Error finding message " + error.code + ": " + error.message);
});
});

Parse Cloud: Query not running in exported function from save() callback

I'm using Parse to represent the state of a beer keg (among other things). I'd like to check the user's notifications, stored in a "Notifications" table, to see if they'd like to receive a notification when the keg is filled.
I have all of the logic for setting the user's notification settings as well as sending notifications in cloud/notifications.js. All of the logic for updating the keg is in cloud/beer.js. I created an exported function called "sendKegRefillNotification" which performs a query.find() on the Notifications table and gets called from beer.js.
The problem is that it doesn't seem to be executing query.find() when I call the function from beer.js, however when I call the same function from a job within notifications.js, it works just fine.
main.js:
require("cloud/beer.js");
require("cloud/notifications.js");
beer.js:
var notify = require("cloud/notifications.js");
var Keg = Parse.Object.extend("Keg");
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
promise.resolve(keg);
notify.sendKegRefillNotification(keg);
},
function(keg, error) {
promise.reject(error);
});
return promise;
}
Parse.Cloud.define("beerFillKeg", function(request, response) {
var beerName = request.params.name;
if (!beerName) {
response.error("No beer was specified.");
return;
}
if (!util.isUserAdmin(request.user)) {
response.error("User does not have permission to update the keg.");
return;
}
fillKeg(beerName).then(function(keg) {
kegResponse(keg).then(function(result) {
response.success(result);
});
},
function(error) {
response.error(error);
});
});
function kegResponse(keg) {
var promise = new Parse.Promise();
var result = {
id: keg.id,
beer: {
name: keg.get("beerName")
},
filled: keg.createdAt,
kickedReports: []
};
var kickedReports = keg.get("kickedReports");
if (!kickedReports || kickedReports.length == 0) {
promise.resolve(result);
} else {
util.findUsers(kickedReports).then(function(users) {
result.kickedReports = util.infoForUsers(users);
promise.resolve(result);
}, function(users, error) {
console.log(error);
promise.resolve(result);
});
}
return promise;
}
notifications.js:
var Keg = Parse.Object.extend("Keg");
var Notification = Parse.Object.extend("Notifications");
exports.sendKegRefillNotification = function(keg) {
var beerName = keg.get("beerName");
console.log("Sending notifications that keg is refilled to '" + beerName + "'.");
var promise = new Parse.Promise();
var query = new Parse.Query(Notification);
query.include("user");
query.equalTo("keg_filled", true);
query.find({ useMasterKey: true }).then(function(notifications) {
console.log("Found notifications!");
promise.resolve("Found notifications!");
},
function(notifications, error) {
console.error("No notifications");
console.error(error);
promise.reject(error);
});
return promise;
}
Parse.Cloud.job("beerSendRefillNotification", function(request, status) {
var query = new Parse.Query(Keg);
query.descending("createdAt");
query.first().then(function(keg) {
if (!keg) {
status.error("No keg");
return;
}
exports.sendKegRefillNotification(keg);
},
function(keg, error) {
response.error(error);
});
});
When I run the job "beerSendRefillNotification" from the Parse dashboard, I can tell that query.find() is getting called because it prints "Found notifications!":
E2015-02-23T06:59:49.006Z]v1564 Ran job beerSendRefillNotification with:
Input: {}
Result: success/error was not called
I2015-02-23T06:59:49.055Z]false
I2015-02-23T06:59:49.190Z]Sending notifications that keg is refilled to 'test'.
I2015-02-23T06:59:49.243Z]Found notifications!
However, when I call the cloud function "beerFillKeg", it isn't because it's not printing "Found notifications!" or "No notifications":
I2015-02-23T07:00:17.414Z]v1564 Ran cloud function beerFillKeg for user HKePOEWZvC with:
Input: {"name":"Duff"}
Result: {"beer":{"name":"Duff"},"filled":{"__type":"Date","iso":"2015-02-23T07:00:17.485Z"},"id":"olLXh0F54E","kickedReports":[]}
I2015-02-23T07:00:17.438Z]false
I2015-02-23T07:00:17.523Z]Keg updated to Duff.
I2015-02-23T07:00:17.525Z]Sending notifications that keg is refilled to 'Duff'.
I finally understand it. In sendKegRefillNotification, you're calling query.find({...}), then returning an object. That find is asynchronous, and you're doing nothing to wait for the result. I think you need to return the find function call, rather than an object you set within that method.
In other words, you're running along, leaving some async running code behind you.
Edit: I understand what you tried to do. It sort of makes sense. You defined a promise, and thought the caller would wait for the promise. The problem is, the promise is defined in an asynchronous block. It doesn't yet have any meaning at the moment the caller gets it.
It looks like Parse doesn't allow you to run a query from inside a callback from save(). When I moved "notify.sendKegRefillNotification(keg);" to outside of the callback, it worked.
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
console.log("Send notifications.");
promise.resolve(keg);
},
function(keg, error) {
promise.reject(error);
});
notify.sendKegRefillNotification(keg); // Now this works
return promise;
}
Can anyone shed some more light on why this worked?

Adding contraints to a column on Parse Data

I'm saving some objects into tables on my Parse Data. But I need to add a constraint or make sure that the data i'm trying to insert is unique. I'm using something like the following code. But i want to guarantee that the eventId (that I'm getting from facebook API) is unique in my tables, so i don't have any redundant information. What is the best way to make it work?
var Event = Parse.Object.extend("Event");
var event = new Event();
event.set("eventId", id);
event.set("eventName", name);
event.save(null, {
success: function(event) {
console.log('New object created with objectId: ' + event.eventId);
},
error: function(event, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
Update:
I'm calling it inside a httpRequest. The following is pretty much what I have and I cant figure out just how to call a beforeSave inside it.
Parse.Cloud.define("hello", function(request, response) {
var query = new Parse.Query("Location");
query.find({
success: function(results) {
console.log(results);
var totalResults = results.length;
var completedResults = 0;
var completion = function() {
response.success("Finished");
};
for (var i = 0; i < totalResults; ++i){
locationId = results[i].get("locationFbId");
Parse.Cloud.httpRequest({
url: 'https://graph.facebook.com/v2.2/'+locationId+'/events?access_token='+accessToken,
success: function(httpResponse) {
console.log(httpResponse.data);
console.log("dsa"+locationId);
for (var key in httpResponse.data) {
var obj = httpResponse.data[key];
for (var prop in obj) {
var eventObj = obj[prop];
if (typeof(eventObj) === 'object' && eventObj.hasOwnProperty("id")) {
var FbEvent = Parse.Object.extend("FbEvent");
var fbEvent = new FbEvent();
fbEvent.set("startDate",eventObj["start_time"]);
fbEvent.set("locationFbId", locationId);
fbEvent.set("fbEventId", eventObj["id"]);
fbEvent.set("fbEventName", eventObj["name"]);
Parse.Cloud.beforeSave("FbEvent", function(request, response) {
var query = new Parse.Query("FbEvent");
query.equalTo("fbEventId", request.params.fbEventId);
query.count({
success: function(number) {
if(number>0){
response.error("Event not unique");
} else {
response.success();
}
},
error: function(error) {
response.error(error);
}
});
});
}
}
}
completedResults++;
if (completedResults == totalResults) {
completion();
}
},
error:function(httpResponse){
completedResults++;
if (completedResults == totalResults)
response.error("Failed to login");
}
});
}
},
error: function() {
response.error("Failed on getting locationId");
}
});
});
So this is occurring in Cloud Code correct? (Im assuming since this is Javascript)
What you could do is create a function that occurs before each "Event" object is saved and run a query to make sure that the event is unique (query based off of "eventId" key, not objectId since the id comes from Facebook). If the event is unique, return response.success(), otherwise return response.error("Event not unique")
EX:
Parse.Cloud.beforeSave("Event", function(request, response) {
if(request.object.dirty("eventId")){
var query = var new Parse.Query("Event");
query.equalTo("eventId", request.object.eventId);
query.count({
success: function(number) {
if(number>0){
response.error("Event not unique");
} else {
response.success();
}
},
error: function(error) {
response.error(error);
}
});
} else {
response.success();
}
});
Parse.Cloud.define("hello", function(request, response) {
var query = new Parse.Query("Location");
query.find({
success: function(results) {
console.log(results);
var totalResults = results.length;
var completedResults = 0;
var completion = function() {
response.success("Finished");
};
for (var i = 0; i < totalResults; ++i){
locationId = results[i].get("locationFbId");
Parse.Cloud.httpRequest({
url: 'https://graph.facebook.com/v2.2/'+locationId+'/events?access_token='+accessToken,
success: function(httpResponse) {
console.log(httpResponse.data);
console.log("dsa"+locationId);
for (var key in httpResponse.data) {
var obj = httpResponse.data[key];
for (var prop in obj) {
var eventObj = obj[prop];
if (typeof(eventObj) === 'object' && eventObj.hasOwnProperty("id")) {
var FbEvent = Parse.Object.extend("FbEvent");
var fbEvent = new FbEvent();
fbEvent.set("startDate",eventObj["start_time"]);
fbEvent.set("locationFbId", locationId);
fbEvent.set("fbEventId", eventObj["id"]);
fbEvent.set("fbEventName", eventObj["name"]);
// Our beforeSave function is automatically called here when we save it (this will happen every time we save, so we could even upgrade our method as shown in its definition above)
fbEvent.save(null, {
success: function(event) {
console.log('New object created with objectId: ' + event.eventId);
},
error: function(event, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
}
}
}
completedResults++;
if (completedResults == totalResults) {
completion();
}
},
error:function(httpResponse){
completedResults++;
if (completedResults == totalResults)
response.error("Failed to login");
}
});
}
},
error: function() {
response.error("Failed on getting locationId");
}
});
});
This can also be accomplished before ever calling the save by querying and only saving if the query returns with a number == 0.
Summary: For those joining later, what we are doing here is checking to see if an object is unique (this time based on key eventId, but we could use any key) by overriding Parse's beforeSave function. This does mean that when we save our objects (for the first time) we need to be extra sure we have logic to handle the error that the object is not unique. Otherwise this could break the user experience (you should have error handling that doesn't break the user experience anyway though).

Got promise not working

I'm trying to use promise to get in promise2
But if I have an object Widgets with several elements in it...
Why can't I have been able to get my console.log's output
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
var promise = Parse.Promise.as();
[...]
}).then(function() {
return query.find().then(function(results) {
_.each(results, function(result) {
[...]
Widget.objectId = result.id;
Widgets[timestamp] = Widget;
});
return promise;
}).then(function(results) {
for (var key in Widgets) {
var Widget = Widgets[key];
var widget_data = Widgets[key].widget_data;
var promise2 = Parse.Promise.as();
promise2 = promise2.then(function() {
return Parse.Cloud.run('extractWidgetData', {
'widget_data': widget_data,
}).then(function(newresult) {
Widgets[key].data = newresult.data;
console.log('--------WHY NOT HERE ALL TIME ?--------');
});
});
return promise2;
}
}).then(function() {
response.success(Widgets);
},
function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});
I'm becoming crazy to run this damn Code
EDIT : I finally followed Roamer's advices to implement something but I'm not sure if it's the good way to work with Promise in series...
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
var Widgets = {};
...
... .then(function() {
return query.find().then(function(results) {
return Parse.Promise.when(results.map(function(result) {
var Widget = ...;//some transform of `result`
Widget.id = ...;//some transform of `result`
var timestamp = createdAtDate.getTime();
...
return Parse.Cloud.run('extractData', {
'widget_data': Widget.widget_data,
}).then(function(newresult) {
Widget.stat = newresult.stats;
return Widget;//<<<<<<< important! This ensures that results.map() returns an array of promises, each of which delivers a Widget objects.
});
}));
}).then(function() {
var promisedWidget = Array.prototype.slice.apply(arguments);
return Parse.Promise.when(promisedWidget.map(function(Widget) {
return Parse.Cloud.run('getWineStats', {
'id': Widget.data.id
}).then(function(stat) {
Widget.stat = stat;
return Widget;
});
}));
}).then(function() {
var promisedWidget = Array.prototype.slice.apply(arguments);
_.each(promisedWidget, function(Widget) {
var createdAtObject = Widget.createdAt;
var strDate = createdAtObject.toString();
var createdAtDate = new Date(strDate);
timestamp = createdAtDate.getTime();
Widgets[timestamp] = Widget;
});
return Widgets;
}).then(function(Widgets) {
response.success(Widgets);
},
function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});
First, I echo Bergi's comment on indentation/matching parenthesis.
But ignoring that for a moment, at the heart of the code you have return query.find().then(...).then(...).then(...) but the flow from the first .then() to the second is incorrect. Besides which, only two .then()s are necessary as the code in the first then is synchronous, so can be merged with the second.
Delete the two lines above for (var key in Widgets) { then at least Widgets will be available to be processed further.
Going slightly further, you should be able to do all the required processing of results in a single loop. There seems to be little pont in building Widgets with _.each(...) then looping through the resulting object with for (var key in Widgets) {...}.
In the single loop, you probably want a Parse.Promise.when(results.map(...)) pattern, each turn of the map returning a promise of a Widget. This way, you are passing the required data down the promise chain rather than building a Widgets object in an outer scope.
Do all this and you will end up with something like this :
Parse.Cloud.define("extract", function(request, response) {
var user = request.params.user;
...
... .then(function() {
return query.find().then(function(results) {
return Parse.Promise.when(results.map(function(result) {
var Widget = ...;//some transform of `result`
...
return Parse.Cloud.run('extractWidgetData', {
'widget_data': Widget.widget_data,
}).then(function(newresult) {
Widget.data = newresult.data;
return Widget;//<<<<<<< important! This ensures that results.map() returns an array of promises, each of which delivers a Widget objects.
});
}));
}).then(function() {
//Here, compose the required Widgets array from this function's arguments
var Widgets = Array.prototype.slice.apply(arguments);//Yay, we got Widgets
response.success(Widgets);
}, function(error) {
response.error("Error: " + error.code + " " + error.message);
});
});
});

Setting a Parse.Object.relation at/after object creation

My Email object (my own custom class) is being written though the relation is not being set on time, any ideas how to chain this properly?
// Create new Email model and friend it
addFriendOnEnter: function(e) {
var self = this;
if (e.keyCode != 13) return;
var email = this.emails.create({
email: this.emailInput.val(),
ACL: new Parse.ACL(Parse.User.current())
});
var user = Parse.User.current();
var relation = user.relation("friend");
relation.add(email);
user.save();
this.emailInput.val('');
}
Thanks!
Gon
Because talking to Parse's servers is asynchronous, Parse.Collection.create uses a Backbone-style options object with a callback for when the object is created. I think what you want to do is:
// Create new Email model and friend it
addFriendOnEnter: function(e) {
var self = this;
if (e.keyCode != 13) return;
this.emails.create({
email: this.emailInput.val(),
ACL: new Parse.ACL(Parse.User.current())
}, {
success: function(email) {
var user = Parse.User.current();
var relation = user.relation("friend");
relation.add(email);
user.save();
self.emailInput.val('');
}
});
}
Got it!
The .create method on the this.emails collection does not actually return an object, so var email was empty. Somehow Parse guess it was an empty object of class Email, so I guess the structure is the only thing that remained once .create did its job.
Instead I retrieve the email object on the server using .query, .equalTo and .first
// Create new Email model and friend it
addFriendOnEnter: function(e) {
var self = this;
if (e.keyCode != 13) return;
this.emails.create({
email: this.emailInput.val(),
ACL: new Parse.ACL(Parse.User.current())
});
var query = new Parse.Query(Email);
query.equalTo("email", this.emailInput.val());
query.first({
success: function(result) {
alert("Successfully retrieved an email.");
var user = Parse.User.current();
var relation = user.relation("friend");
relation.add(result);
user.save();
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
this.emailInput.val('');
}

Resources