I'm trying to set up a game that allows playing with random players. The code below is supposed to create a GameMessage object for both paired players. To relate both objects as part of the same game, I've decided to save the objectId of of the game made for "firstplayer" in the field "otherside" for "secondplayer" and vice-versa. For some reason (perhaps the first save of firstplayer and secondplayer isn't done before the code attempts to retrieve the objectIds, meaning there are no objectIds to get?).
Short version: Why are the "otherside" values not saving?
Parse.Cloud.define("findpartner", function(request, response) {
var User = Parse.Object.extend("_User");
var user = new User();
var currentuser = Parse.User.current();
currentuser.set("searching", 0);
var query = new Parse.Query(User);
query.equalTo("searching", 1);
query.limit(50); //limit to at most 50 users
query.find({
success: function(objects) {
var amount = objects.length;
var indexNum = Math.floor((Math.random() * amount));
var newpartner = objects[indexNum];
if (amount > 0 && newpartner.id !=currentuser.id) {
newpartner.set("searching", 0);
var Firstplayer = Parse.Object.extend("GameMessages");
var firstplayer = new Firstplayer();
var Secondplayer = Parse.Object.extend("GameMessages");
var secondplayer = new Secondplayer();
firstplayer.set("sender", currentuser.id);
firstplayer.set("receiver", newpartner.id);
firstplayer.set("sent",0);
firstplayer.set("received",0);
firstplayer.set("receiverName", newpartner.getUsername());
secondplayer.set("sender", newpartner.id);
secondplayer.set("receiver", currentuser.id);
secondplayer.set("sent",0);
secondplayer.set("received",0);
secondplayer.set("receiverName", currentuser.getUsername());
firstplayer.save().then(function(secondplayer){ <<<
return secondplayer.save(); <<<
}).then(function(firstplayer_update) { <<<
return firstplayer.save({ otherside: secondplayer.id}); <<<
}).then(function(secondplayer_update){ <<<
return secondplayer.save({ otherside: firstplayer.id}); <<<
});
newpartner.save(null, {useMasterKey: true});
}
else {
currentuser.set("searching", 1);
}
currentuser.save();
response.success(amount);
},
error: function(error) {
alert("Error: " + error.code = " " + error.message);
}
});
});
I added arrows to show where the "otherside" is. They're not in the actual code. I do not doubt the code has mistakes though, I do not know javascript. I wrote it solely by studying the parse.com documentation.
I'm not convinced that it makes sense to create these 2 independent messages and link them together, but I won't let that stand in the way of getting this working. This isn't tested, but I've refactored your code and think you should try to glean a few things from it.
// Set this up once, outside of your function, and use it everywhere
var GameMessage = Parse.Object.extend("GameMessages");
Parse.Cloud.define("findpartner", function(request, response) {
// Code defensively, make sure this function requires a user be logged in.
if (!request.user) {
console.log("non-user called findpartner");
return response.error("Unauthorized.");
}
// Get the user who called the function
var user = request.user;
// The end response is a number, apparently
var result = 0;
// The target player
var targetPlayer;
// The two messages that will be used if a match is found
var firstmsg = new GameMessage();
var secondmsg = new GameMessage();
// Create a Users query
var query = new Parse.Query(Parse.User);
query.equalTo("searching", 1);
query.notEqualTo("objectId", user.id);
query.limit(50);
// Remove public access to Find operations for Users in the Data Browser
// Use the master key to query, and use promise syntax.
query.find({ useMasterKey: true }).then(function(objects) {
result = objects.length;
// If no users were found searching, mark the user as searching and save
if (result == 0) {
user.set('searching', 1);
// Return the save promise
return user.save(null, { useMasterKey: true });
}
// Pick a random user out of the response
var indexNum = Math.floor((Math.random() * objects.length));
var targetPlayer = objects[indexNum];
// Set that user to no longer be searching and save
targetPlayer.set("searching", 0);
return targetPlayer.save(null, { useMasterKey: true }).then(function() {
firstmsg.set("sender", user.id);
firstmsg.set("receiver", targetPlayer.id);
firstmsg.set("sent", 0);
firstmsg.set("received", 0);
firstmsg.set("receiverName", targetPlayer.getUsername());
secondmsg.set("sender", targetPlayer.id);
secondmsg.set("receiver", user.id);
secondmsg.set("sent", 0);
secondmsg.set("received", 0);
secondmsg.set("receiverName", user.getUsername());
// Return the promise result of saving both messages
return Parse.Object.saveAll([firstmsg, secondmsg], { useMasterKey: true });
}).then(function(messages) {
// Set the pointers to reference each other
firstmsg.set("otherside", secondmsg.id);
secondmsg.set("otherside", firstmsg.id);
// Return the promise result of saving both messages, again
return Parse.Object.saveAll([firstmsg, secondmsg], { useMasterKey: true });
});
}).then(function() {
// All the stuff above has finished one way or the other, now we just need to
// send back the result. 0 if no match was made.
response.success(result);
}, function(error) {
response.error(error);
});
});
firstplayer.save();
secondplayer.save();
secondplayer.set("otherside",firstplayer.id); <<<
firstplayer.set("otherside",secondplayer.id); <<<
firstplayer.save();
secondplayer.save();
This is the part of code that you say not working. In parse doc you can see that .save() is a non blocking operation. Means the line firstplayer.save() goes immediately to next line(it wont block the thread for saving). So when you set id secondplayer.set("otherside",firstplayer.id) firstplayer.id is still undefined.
So if you want a synchronous logic, like save first_object then save second_object ,
you have to use call backs.
first_object.save({
success: function(saved_first_object) {
second_object.save({
success: function(saved_second_object) {
//process complete
},
failure: function(error){
}
})
},
failure: function(error) {
console.log(error);
}
})
You can also approach it using promises.
http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
UPDATE: Based on question edit from OP trying promises
Try this
firstplayer.save()
.then(function(saved_firstPlayer){
firstplayer = saved_firstPlayer;
return secondplayer.save();
}).then(function(saved_secondplayer) {
secondplayer = saved_secondplayer;
return firstplayer.save({ otherside: secondplayer.id});
}).then(function(updated_firstplayer){
firstplayer = updated_firstplayer;
return secondplayer.save({ otherside: firstplayer.id});
}).then(function(updated_secondlayer){
secondplayer= update_secondplayer;
});
Related
Parse-server doesn't support groupBy for queries. So instead of adapting code to work with the duplicate entries i've decided to create a Job to clean the data.
I've created a cloud function using underscore but the results are not good. It's deleting non-duplicate entries too.
I want to remove a entry if another entry exists with the same post_id and user_id
Parse.Cloud.job("removeDuplicateItems", function(request, status) {
var _ = require("underscore");
var hashTable = {};
function hashKeyForTestItem(testItem) {
var fields = ["user_id", "post_id"];
var hashKey = "";
_.each(fields, function (field) {
hashKey += testItem.get(field) + "/" ;
});
return hashKey;
}
var testItemsQuery = new Parse.Query("Post_shares");
testItemsQuery.each(function (testItem) {
var key = hashKeyForTestItem(testItem);
if (key in hashTable) { // this item was seen before, so destroy this
return testItem.destroy();
} else { // it is not in the hashTable, so keep it
hashTable[key] = 1;
}
}).then(function() {
status.success("removal completed successfully.");
}, function(error) {
status.error("Uh oh, something went wrong.");
});
});
Is there a better way of doing this?
I want to solution for nested queries.
Actually, i want to get all reviews for each movie.
I have two tables:
Movie:
Review:
It's possible to calculate rating for each movie and set it to avgRating?
I don't know why, but i can't get all reviews for one movie.
Parse.Cloud.define("setAvg", function(request, response) {
var Movie = Parse.Object.extend("Movie");
var MovieReview = Parse.Object.extend("MovieReview");
var query = new Parse.Query(Movie);
query.each(function(movie){
var reviewQuery = new Parse.Query(MovieReview);
reviewQuery.equalTo("relatedMovie", movie);
reviewQuery.find({
success: function(reviews){
console.log(reviews);
}
});
}).then(function() {
response.success("Migration completed successfully.");
}, function(error) {
response.error("Uh oh, something went wrong.");
});
});
I getting in logs:
I2015-12-21T17:34:57.777Z][]
I2015-12-21T17:34:57.778Z][{}]
I2015-12-21T17:34:57.779Z][{},{}]
I2015-12-21T17:34:57.780Z][{},{}]
I2015-12-21T17:34:57.781Z][{},{},{}]
I2015-12-21T17:34:57.782Z][{},{}]
Parse.Cloud.define("setAvg", function(request, response) {
var Movie = Parse.Object.extend("Movie");
var MovieReview = Parse.Object.extend("MovieReview");
var reviewQuery = new Parse.Query(MovieReview);
var query = new Parse.Query(Movie);
query.equalTo("relatedMovie",reviewQuery);
query.find(function(movie){
//do whatever you want with the query results
});
});
I can see several potential issues :
You should use a background job instead of a function to ensure you have enough time
You should define the reviewQuery inside the each block to avoid mixing
If you can have more than 100 ratings for one movie, you should set the limit of query to 1000 (which is the maximum limit - if there could be more than 1000, you will have to make the same query several times while incrementing the skip parameter).
The solution was as simple as possible.
Thank all for answers.
Parse.Cloud.job("setMovieRating", function(request, response) {
Parse.Cloud.useMasterKey();
var Movie = Parse.Object.extend("Movie");
var MovieReview = Parse.Object.extend("MovieReview");
var query = new Parse.Query(Movie);
query.each(function(movie){
var reviewQuery = new Parse.Query(MovieReview);
reviewQuery.equalTo("relatedMovie", movie);
return reviewQuery.find().then(function(reviews){
var avgRating = 0;
for (var i=0; i<reviews.length;i++){
avgRating+=reviews[i].get('starRating');
}
avgRating = avgRating/reviews.length;
var floatRating = Math.floor(avgRating * 100) / 100;
movie.set('avgRating', parseFloat(floatRating.toFixed(1)));
console.log(movie);
movie.save();
});
}).then(function() {
response.success("Success");
}, function(error) {
response.error("Uh oh, something went wrong.");
});
});
I create a cloud function that process all the items in a specific order and then make a network request to an API for each item and update them with the result of the API.
I tried to do something like this. But the order is not respected and the computation of the "remainingCredits" is wrong.
Note that the function "getUserPageView()" make an API call and return a promise with the result.
query.descending("createdAt");
return query.find().then(function(items) {
var remainingCredits=0;
var promises= _.map(items, function(item){
var credit=item.get("credit_buy");
return getUserPageView(123, new Date()).then(function(pageviews){
var usedCredit=credit-pageviews;
if(remainingCredits>0)
return remainingCredits+credit;
if(credit-usedCredit<=0){
console.log("usedCredit:"+usedCredit);
item.set("used",true);
return 0;
}else{
remainingCredits+=usedCredit;
return remainingCredits;
}
});
});
return Parse.Promise.when(promises);
}).then(function(results){
console.log(_.toArray(arguments));
status.success();
}, function(error){
status.error(error);
});
How can I calculate the remainingCredits with a specific order?
If it is important to process the asynchronously derived data in the original items order, then it should not be processed as it arrives.
Try waiting for all the data to arrive, then process it.
query.descending("createdAt");
return query.find().then(function(items) {
return Parse.Promise.when(_.map(items, function(item) {
return getUserPageView(123, new Date()).then(function(pageviews) {
//here, create an object with all the data required for later processing
return {
item: item,
pageviews: pageviews
};
});
}));
}).then(function() {
var remainingCredits = 0;
var runningBalance _.toArray(arguments).map(function(obj) {
var credit = obj.item.get("credit_buy");
var usedCredit = credit - obj.pageviews;
if(remainingCredits > 0)
return remainingCredits + credit;
if((credit - usedCredit) <= 0) {
console.log("usedCredit:" + usedCredit);
obj.item.set("used", true);
return 0;
} else {
remainingCredits += usedCredit;
return remainingCredits;
}
});
console.log(runningBalance);
status.success();
return runningBalance;
}, function(error) {
status.error(error);
});
I invented the variable name runningBalance because that is what you appear to be trying to create.
I tried to verify the processing of credits and can only suggest that it needs looking at again. For example, if usedCredit = credit - obj.pageviews, then the test if((credit - usedCredit) <= 0) {...} will simplify to if(obj.pageviews <= 0) {...}, which doesn't seem right.
I have a forloop like this:
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname);
myphone.get(function(phonenumbers){
if(myphone.phonearray){
myperson.save();
//Can I put a break here?;
}
});
}
What it does is that it searches for phone-numbers in a database based on various first-names. What I want to achieve is that once it finds a number associated with any of the first names, it performs myperson.save and then stops all the iterations, so that no duplicates get saved. Sometimes, none of the names return any phone-numbers.
myphone.get contains a server request and the callback is triggered on success
If I put a break inside the response, what will happen with the other iterations of the loop? Most likely the other http-requests have already been initiated. I don't want them to perform the save. One solution I have thought of is to put a variable outside of the forloop and set it to save, and then check when the other callbacks get's triggered, but I'm not sure if that's the best way to go.
You could write a helper function to restrict invocations:
function callUntilTrue(cb) {
var done = false;
return function () {
if (done) {
log("previous callback succeeded. not calling others.");
return;
}
var res = cb.apply(null, arguments);
done = !! res;
};
}
var myperson = {
firstname: {
"tom": null,
"jerry": null,
"micky": null
},
save: function () {
log("save " + JSON.stringify(this, null, 2));
}
};
var cb = function (myperson_, phonenumbers) {
if (myperson_.phonearray) {
log("person already has phone numbers. returning.");
return false;
}
if (phonenumbers.length < 1) {
log("response has no phone numbers. returning.");
return false;
}
log("person has no existing phone numbers. saving ", phonenumbers);
myperson_.phonearray = phonenumbers;
myperson_.save();
return true;
};
var restrictedCb = callUntilTrue(cb.bind(null, myperson));
for (var name in myperson.firstname) {
var myphone = new phone(myperson, name);
myphone.get(restrictedCb);
}
Sample Console:
results for tom-0 after 1675 ms
response has no phone numbers. returning.
results for jerry-1 after 1943 ms
person has no existing phone numbers. saving , [
"jerry-1-0-number"
]
save {
"firstname": {
"tom": null,
"jerry": null,
"micky": null
},
"phonearray": [
"jerry-1-0-number"
]
}
results for micky-2 after 4440 ms
previous callback succeeded. not calling others.
Full example in this jsfiddle with fake timeouts.
EDIT Added HTML output as well as console.log.
The first result callback will only ever happen after the loop, because of the single-threaded nature of javascript and because running code isn't interrupted if events arrive.
If you you still want requests to happen in parallel, you may use a flag
var saved = false;
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname /* name? */);
myphone.get(function(phonenumbers){
if (!saved && myphone.phonearray){
saved = true;
myperson.save();
}
});
}
This will not cancel any pending requests, however, just prevent the save once they return.
It would be better if your .get() would return something cancelable (the request itself, maybe).
var saved = false;
var requests = [];
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname /* name? */);
var r;
requests.push(r = myphone.get(function(phonenumbers){
// Remove current request.
requests = requests.filter(function(i) {
return r !== i;
});
if (saved || !myphone.phonearray) {
return;
}
saved = true;
// Kill other pending/unfinished requests.
requests.forEach(function(r) {
r.abort();
});
myperson.save();
}));
}
Even better, don't start all requests at once. Instead construct an array of all possible combinations, have a counter (a semaphore) and only start X requests.
var saved = false;
var requests = [];
// Use requests.length as the implicit counter.
var waiting = []; // Wait queue.
for (var name in myperson.firstname){
var myphone = new phone(myperson, firstname /* name? */);
var r;
if (requests.length >= 4) {
// Put in wait queue instead.
waiting.push(myphone);
continue;
}
requests.push(r = myphone.get(function cb(phonenumbers){
// Remove current request.
requests = requests.filter(function(i) {
return r !== i;
});
if (saved) {
return;
}
if (!myphone.phonearray) {
// Start next request.
var w = waiting.shift();
if (w) {
requests.push(w.get(cb));
)
return;
}
saved = true;
// Kill other pending/unfinished requests.
requests.forEach(function(r) {
r.abort();
});
myperson.save();
}));
}
I had a read of the meme example but it doesn't seem to update, just create new objects! What I want is to
a. find some given db table
b. update some fields in the db table
c. save the db table back to the database
Given this code, what is the missing piece so that I can actually update an object?
query.find(
function(results){
if (results.length > 0){
return results[0];
} else {
//no object found, so i want to make an object... do i do that here?
return null;
}
},
function(error){
response.error("ServerDown");
console.error("ServerDown - getModuleIfAny URGENT. Failed to retrieve from the ModuleResults table" + +error.code+ " " +error.message);
}
).then(
function(obj){
var module;
if (obj != null){
console.log("old");
module = obj;
module.moduleId = 10; //let's just say this is where i update the field
//is this how i'd update some column in the database?
} else {
console.log("new");
var theModuleClass = Parse.Object.extend("ModuleResults");
module= new theModuleClass();
}
module.save().then(
function(){
response.success("YAY");
},
function(error) {
response.error('Failed saving: '+error.code);
}
);
},
function(error){
console.log("sod");
}
);
I thought the above code would work - but it does not. When it finds an object, it instead refuses to save, stupidly telling me that my object has no "save" method.
First I would double check the version of the javascript sdk you're using in your cloud code. Make sure it's up to date e.g. 1.2.8. The version is set in the config/global.json file under your cloud code directory.
Assuming you're up to date I would try modifying your code by chaining the promises using multiple then's like so:
query.find().then(function(results){
if (results.length > 0){
return results[0];
} else {
//no object found, so i want to make an object... do i do that here?
return null;
}
},
function(error){
response.error("ServerDown");
console.error("ServerDown - getModuleIfAny URGENT. Failed to retrieve from the ModuleResults table" + +error.code+ " " +error.message);
}).then(function(obj){
var module;
if (obj != null){
console.log("old");
module = obj;
module.moduleId = 10; //let's just say this is where i update the field
//is this how i'd update some column in the database?
} else {
console.log("new");
var theModuleClass = Parse.Object.extend("ModuleResults");
module= new theModuleClass();
}
module.save();
}).then(function(result) {
// the object was saved.
},
function(error) {
// there was some error.
});
I think this should work. Fingers crossed. Cheers!