so i have a messaging app using parse.com as my backend. When i send a message from the app it saves it on Parse.com to a class called "NewMessages". Then in my cloud code i have an afterSave function dedicated to this class so that when a new object gets saved to "NewMessages" it picks a random user attaches it to the message and saves it in a new class called "Inbox". Then it deletes the original message from "NewMessages".
So the "NewMessages" class should always be empty right? But when I send a bunch of messages very quickly some get skipped over. How do i fix this?
Is there a better way to structure this than using afterSave?
function varReset(leanBody, leanSenderName, leanSenderId, randUsers){
leanBody = "";
leanSenderName = "";
leanSenderId = "";
randUsers = [];
console.log("The variables were set");
}
Parse.Cloud.afterSave("Lean", function(leanBody, leanSenderName, leanSenderId, randUsers, request) {
varReset(leanBody, leanSenderName, leanSenderId, randUsers);
var query = new Parse.Query("NewMessages");
query.first({
success: function(results){
leanBody = (results.get("MessageBody"));
leanSenderName = (results.get("senderName"));
leanSenderId = (results.get("senderId"));
getUsers(leanBody, leanSenderName, leanSenderId);
results.destroy({
success: function(results){
console.log("deleted");
}, error: function(results, error){
}
});
}, error: function(error){
}
});
});
function getUsers(leanBody, leanSenderName, leanSenderId, response){
var query = new Parse.Query(Parse.User);
query.find({
success: function(results){
var users = [];
console.log(leanBody);
console.log(leanSenderName);
//extract out user names from results
for(var i = 0; i < results.length; ++i){
users.push(results[i].id);
}
for(var i = 0; i < 3; ++i){
var rand = users[Math.floor(Math.random() * users.length)];
var index = users.indexOf(rand);
users.splice(index, 1);
randUsers.push(rand);
}
console.log("The random users are " + randUsers);
sendMessage(leanBody, leanSenderName, leanSenderId, randUsers);
}, error: function(error){
response.error("Error");
}
});
}
function sendMessage(leanBody, leanSenderName, leanSenderId, randUsers){
var Inbox = Parse.Object.extend("Inbox");
for(var i = 0; i < 3; ++i){
var inbox = new Inbox();
inbox.set("messageBody", leanBody);
inbox.set("senderName", leanSenderName);
inbox.set("senderId", leanSenderId);
inbox.set("recipientId", randUsers[i]);
console.log("leanBody = " + leanBody);
console.log("leanSenderName = " + leanSenderName);
console.log("leanSenderId = " + leanSenderId);
console.log("recipient = " + randUsers[i]);
inbox.save(null, {
success: function(inbox) {
// Execute any logic that should take place after the object is saved.
alert('New object created with objectId: ' + inbox.id);
},
error: function(inbox, error) {
// Execute any logic that should take place if the save fails.
// error is a Parse.Error with an error code and message.
alert('Failed to create new object, with error code: ' + error.message);
}
});
}
}
Have you checked your logs? You may be falling afoul of resource limits (https://parse.com/docs/cloud_code_guide#functions-resource). If immediacy is not important, it may be worth looking into set up a background job that runs every few minutes and tackles undelivered messages. It may also be possible to combine the two approaches: having the afterSave function attempt to do an immediate delivery to Inboxes, while the background job picks up any NewMessages left over on a regular basis. Not the prettiest solution but at least you have a bit more reliability. (You'll have to think about race conditions though where the two may attempt deliveries on the same NewMessage.)
Regarding your question about a better structure, if the two classes are identical (or close enough), is it possible to just have a Messages class? Initially the "to" field will be null but is assigned a random recipient on a beforeSave function. This may be faster and neater.
EDIT: Adding a 3rd observation which was originally a comment:
I saw that you are using a Query.first() in afterSave in order to find the NewMessage to take care of. Potentially, a new NewMessage could have snuck in between the time afterSave was called, and the Query was run. Why not get the ID of the saved NewMessage and use that in the Query, instead of first()?
query.get(request.object.id,...);
This ensures that the code in afterSave handles the NewMessage that it was invoked for, not the one that was most recently saved.
Related
I'm using Parse.com as my backend and after Query how can I fill an array with all the data inside the Parse object? how can I avoid re-mapping? example:
$scope.addContList = contacts.map(function(obj) { // re-map!!!!
return {name: obj.get("name")}; // mapping object using obj.get()
});
I'm mapping my Parse object's properties one by one: name: obj.get("name"), etc. is there a better way?
$scope.addContList = [];
var ActivityContact = Parse.Object.extend("ActivityContact2");
var query = new Parse.Query(ActivityContact);
query.equalTo("activityId", $scope.objId);
query.find({
success: function(contacts) {
console.log("Successfully retrieved " + contacts.length + " contact.");
$scope.$apply(function() {
/*$scope.addContList = contacts.map(function(obj) {
return {name: obj.get("name")}; // mapping object using obj.get()
});*/
for (var i = 0; i < contacts.length; i++) {
$scope.addContList.push(contacts.ALL_PROPERTIES); // contacts.ALL_PROPERTIES does not exist, I'm looking a way to do that and avoid mapping?
}
});
console.log("--->>>"+JSON.stringify($scope.addContList, null, 4));
},
error: function(object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
}
});
Should I use Underscore library, is that the only way to go?
I have seen some ppl using PFQuery but I don't know what is that, is PFQuery better for this?
Thanks!
The other answers are correct, but I think it's unnecessary to launch a digest cycle every time you add an item from contacts to $scope.addContList. Something like this should be sufficient:
query.find({
success: function (contacts) {
$scope.apply(function () {
// 1) shallow-copy the list of contacts...
// (this is essentially what you are trying to do now)
$scope.addContList = contacts.slice();
// or 2) just assign the reference directly
$scope.addContList = contacts;
// or 3) transform the Parse.Object instances into
// plain JavaScript objects
$scope.addContList = contacts.map(function (c) {
return c.toJSON();
});
});
},
error: function (object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
}
});
Options 1) and 2) will correspond to a template similar to
<div ng-repeat="cont in addContList">{{ cont.get('name') }}</div>
while option 3) can be used like
<div ng-repeat="cont in addContList">{{ cont.name }}</div>
If you change
$scope.addContList = contacts[i];
to:
$scope.addContList.push(contacts[i]);
you should be good to go. Your previous code was re-assigning addContList to be each element in the contacts array, instead of adding the element to it. So at the end of your for loop, $scope.addContList would just be the last contact in your contacts array.
Change:
$scope.addContList = contacts[i];
to
$scope.addContList.push(contacts[i]);
i have a background job that is trying to update user balance with a new balance if they won the league
This functions are helper functions which are being called properly but "query.first" in updateUserEarnings function and "then" function inside updateWinningsHelper are never called, at least nothing is shown in log, . And yes I am user masterkey.
var updateUserEarnings = function (User,prizeValue){
//var user = new Parse.User();
console.log("updateUserEarnings Called");
console.log("User Id: " + User.id);//Shown in log fi
console.log("PrizeValue " + prizeValue);//Shown in log file
var query = new Parse.Query(Parse.User);
query.equalTo("objectId",User.id);
return query.first({
success: function(result){
console.log("updateUserEarnings success");//Never Reached here
var balance;
if (result.get("cashBalance")){
balance = result.get("cashBalance");
}else{
balance = 0;
}
console.log("Current Balance "+ balance + " for "+result.get("DisplayUsername"));
balance = balance + prizeValue;
result.set("cashBalance",balance);
console.log("User " + result.get("DisplayUsername") + " Balance Updated : " + balance);
return result
},error:function(error){
console.log("user update error "+error);//never reached here as well
return error;
}
})
}
var updateWinningsHelper = function(index,lastIndex,record){
var winningObj = record.winningObject;
console.log("Winning object "+ JSON.stringify(winningObj));//Shown in log
console.log("Index : "+index + " lastIndex : "+lastIndex);//Shown in log
if (index < lastIndex){
updateUserEarnings(winningObj.get("User"), winningObj.get("League").get("PrizeValue")).then(
function(user){
console.log("Inside then function");//Never Reached
saveAllObjects.push(user);
},
function(error){
console.log("Error Happened "+error );//never reached
}
);
winningObj.set("LeagueWon",true);
winningObj.set("isRedeemed",true);
}else{
winningObj.set("LeagueWon",false);
}
index++;
winningObj.set("Rank",index);
return winningObj;
};
Any help is appreciated
Turns out you don't need to query again if you already have User pointer from main query. I included User column with the main query and I can use regular set function to that user.
all i did was
var updateUserEarnings = function (User,prizeValue){
//var user = new Parse.User();
User.set("cashBalance", User.get("cashBalance")+prizeValue);
}
hope this helps anyone in the future and not waste 5 hours like me.
Glad you found the problem!
While on the topic, you may want to look into removing any confusion caused by mixing javascript programming paradigms. You use the old callback paradigm as well as the newer Promises for asynchronous operations, which is a mess to debug.
Parse Engineering on JS Promises
I looked at your code for a fair bit of time before realizing how you were trying to perform the query's old-style callback, then return a promise from the query to the calling function, and then use the promise callback in the update helper.
I'm working on my first simple chat application and this issue has me stuck. I know what I'm trying to do, but I end up overthinking it.
Basically, I have this heroku server going:
http://tiy-fee-rest.herokuapp.com/collections/blabberTalk
Whenever someone sends a message, it is added to this array.
My Issue:
I have it on a set interval so that every 2 seconds, it runs the getNewestMessages function. When this setInterval is working and someone sends a message, it will keep appending the last message they sent every 2 seconds. If I disable the setInterval and simply call the getNewestMessages function myself in a separate browser tab, this doesn't seem to happen. I want to make it so that the most recently sent message isn't constantly re-appended to the DOM when the setInterval is active.
This is the function I'm using to check for recent messages. It's pretty bloated, sorry about that:
getNewestMessages: function() {
$.ajax({
url: http://tiy-fee-rest.herokuapp.com/collections/blabberTalk,
method: 'GET',
success: function (data) {
// Finds Id of most recent message displayed in the DOM
var recentId = $('.message').last().data('id');
var prevMostRecent = 0;
var newMostRecent = [];
jQuery.each(data, function(idx,el){
if (el._id === recentId) {
// if one of the messages on the server has an Id equal to
// one of the messages in the DOM, it saves its index in a var
prevMostRecent = idx;
}
});
jQuery.each(data, function(idx,el){
if (idx < prevMostRecent) {
// if there are messages on the server with a lower index than
// the most recent message in the DOM, it pushes them to a new
// array. Basically, creates a new array of any messages newer
// than the last one displayed in the DOM.
newMostRecent.push(el);
}
});
for (var i = 0; i < newMostRecent.length; i++) {
console.log(newMostRecent[i]);
if (newMostRecent[i]._id === $('.message').last().data('id')) {
// My attempt at trying to remove the last DOM message from
// the array of newer messages. My main issue was that this
// whole function would keep appending the most recent message
// over and over again.
var result = _.without(newMostRecent, newMostRecent[i]);
console.log('MESSAGE TO BE EXCLUDED: ', newMostRecent[i]);
// If the array of newer messages contained the most recent
// DOM message, it removes it and sends it to be appended.
page.appendNewestMessages(result);
}
}
// If the array of newer messages DOESN'T contain the most recent
// DOM message, it just sends the whole array normally.
page.appendNewestMessages(newMostRecent);
},
error: function (err) {
}
});
}
Here is the append function:
appendNewestMessages: function(messagesToAppend) {
console.log(messagesToAppend.reverse());
_.each(messagesToAppend.reverse(), function(el, idx, arr) {
var newMessage = {
content: el.content,
timestamp: el.timestamp,
author: el.author,
userIcon: el.userIcon
}
$.ajax({
url: page.url,
method: 'POST',
data: newMessage,
success: function (data) {
page.addOneMessageToDOM(data);
},
error: function (err) {
console.log("error ", err);
}
});
})
}
Can anyone help me understand how to get the most recent messages from a server and append them to the DOM without any repeats? This has been driving me nuts.
Thanks for any and all help.
I am working on a simple iOS game using parse.com as my backend.
I would like to increment the scores of all PFUsers playing a game when the game ends, NOT just currentUser.
I call my cloud code using objective c as follows. (objects is an array which contains all the users in the game)
for (PFObject *object in objects){
int count = 100;
NSNumber *addToScore = [NSNumber numberWithInt:count];
[PFCloud callFunction:#"incrementUserScore" withParameters:#{
#"userId":object.objectId, #"baseNumber":addToScore }];
}
The cloud code itself is given below
Parse.Cloud.define('incrementUserScore', function(request, response) {
var userId = request.params.userId,
addToScore = request.params.baseNumber,
User = Parse.Object.extend('_User'),
user = new User({ objectId: userId });
user.increment("score",addToScore);
Parse.Cloud.useMasterKey();
user.save().then(function(user) {
response.success(user);
}, function(error) {
response.error(error)
});
});
My game crashes when the cloud code is called.
I am a beginner programer and have not used javascript much at all.
Does anybody see anything obviously wrong?
So your cloud code is the issue. You need to create a query for each user object, and from that query set the score and save.
There is all of this information at:
https://parse.com/docs/cloud_code_guide#functions
You'll want to fetch the user object based on the userId you're passing in and then change that object's score to the appropriate value. Below is a sample cloud function from the documentation that should give you a start for the query part of this task.
Parse.Cloud.define("averageStars", function(request, response) {
var query = new Parse.Query("Review");
query.equalTo("movie", request.params.movie);
query.find({
success: function(results) {
var sum = 0;
for (var i = 0; i < results.length; ++i) {
sum += results[i].get("stars");
}
response.success(sum / results.length);
},
error: function() {
response.error("movie lookup failed");
}
});
});
In my Win 8 app, based on a blank template, I have successfully added search contract and it seems to work despite the fact that I have not linked it to any data yet, so, for now, when I search any term in my app it simply takes me to the searchResults page with the message "No Results Found" this is what I was expecting initially.
Now what I wish to do is link my database into the searchResults.js file so that I can query my database. Now outside of the search contract I have tested and connected my Db and it works; I did this using WinJS.xhr, to connect to my web-service which in turn queries my database and returns a JSON object.
In my test I only hardcoded the url, however I now need to do two things. Move the test WinJS.xr data for connecting my DB into the search contract code, and second - change the hardcoded url to a dynamic url that accepts the users search term.
From what I understand of Win 8 search so far the actual data querying part of the search contract is as follows:
// This function populates a WinJS.Binding.List with search results for the provided query.
_searchData: function (queryText) {
var originalResults;
// TODO: Perform the appropriate search on your data.
if (window.Data) {
originalResults = Data.items.createFiltered(function (item) {
return (item.termName.indexOf(queryText) >= 0 || item.termID.indexOf(queryText) >= 0 || item.definition.indexOf(queryText) >= 0);
});
} else {`enter code here`
originalResults = new WinJS.Binding.List();
}
return originalResults;
}
});
The code that I need to transfer into this section is as below; now I have to admit I do not currently understand the code block above and have not found a good resource for breaking it down line by line. If someone can help though it will be truly awesome! My code below, I basically want to integrate it and then make searchString be equal to the users search term.
var testTerm = document.getElementById("definition");
var testDef = document.getElementById("description");
var searchString = 2;
var searchFormat = 'JSON';
var searchurl = 'http://www.xxx.com/web-service.php?termID=' + searchString +'&format='+searchFormat;
WinJS.xhr({url: searchurl})
.done(function fulfilled(result)
{
//Show Terms
var searchTerm = JSON.parse(result.responseText);
// var terms is the key of the object (terms) on each iteration of the loop the var terms is assigned the name of the object key
// and the if stament is evaluated
for (terms in searchTerm) {
//terms will find key "terms"
var termName = searchTerm.terms[0].term.termName;
var termdefinition = searchTerm.terms[0].term.definition;
//WinJS.Binding.processAll(termDef, termdefinition);
testTerm.innerText = termName;
testDef.innerText = termdefinition;
}
},
function error(result) {
testDef.innerHTML = "Got Error: " + result.statusText;
},
function progress(result) {
testDef.innerText = "Ready state is " + result.readyState;
});
I will try to provide some explanation for the snippet that you didn't quite understand. I believe the code you had above is coming from the default code added by Visual Studio. Please see explanation as comments in line.
/**
* This function populates a WinJS.Binding.List with search results
* for the provided query by applying the a filter on the data source
* #param {String} queryText - the search query acquired from the Search Charm
* #return {WinJS.Binding.List} the filtered result of your search query.
*/
_searchData: function (queryText) {
var originalResults;
// window.Data is the data source of the List View
// window.Data is an object defined in YourProject/js/data.js
// at line 16 WinJS.Namespace.defineļ¼"Data" ...
// Data.items is a array that's being grouped by functions in data.js
if (window.Data) {
// apply a filter to filter the data source
// if you have your own search algorithm,
// you should replace below code with your code
originalResults = Data.items.createFiltered(function (item) {
return (item.termName.indexOf(queryText) >= 0 ||
item.termID.indexOf(queryText) >= 0 ||
item.definition.indexOf(queryText) >= 0);
});
} else {
// if there is no data source, then we return an empty WinJS.Binding.List
// such that the view can be populated with 0 result
originalResults = new WinJS.Binding.List();
}
return originalResults;
}
Since you are thinking about doing the search on your own web service, then you can always make your _searchData function async and make your view waiting on the search result being returned from your web service.
_searchData: function(queryText) {
var dfd = new $.Deferred();
// make a xhr call to your service with queryText
WinJS.xhr({
url: your_service_url,
data: queryText.toLowerCase()
}).done(function (response) {
var result = parseResultArrayFromResponse(response);
var resultBindingList = WinJS.Binding.List(result);
dfd.resolve(result)
}).fail(function (response) {
var error = parseErrorFromResponse(response);
var emptyResult = WinJS.Binding.List();
dfd.reject(emptyResult, error);
});
return dfd.promise();
}
...
// whoever calls searchData would need to asynchronously deal with the service response.
_searchData(queryText).done(function (resultBindingList) {
//TODO: Display the result with resultBindingList by binding the data to view
}).fail(function (resultBindingList, error) {
//TODO: proper error handling
});