Update Object or Create it if it doesn't exists? - javascript

Im digging though some cloud code to do some manipulation of data and save it as a new class.
I have a situation where I read a row from another class, do some math functions, then save the manipulated data to another class which is then read by our client.
The problem is that If the other object already exists in the new class, I just want to update it instead of creating a new one. I know in the parse documentation
it lists creating an object and updating but not really functionality to update if exists, and if not create.
Here is just some example code.. the out data is the data prepped to save for the new class. I can crate the new class object, but when I update some value that
should trigger a update instead of a create new is where things fall apart.
Please understand JS is not my first language so this might be hacked or completely going about it the wrong way, but I should stress I do not know the objectId of
the new class.
if(out.length > 0) {
var game = Parse.Object.extend("Gamers");
var query = new Parse.Query(game);
query.equalTo("playername", player); // using this to find the player since I dont have the objectid
query.find({
success: function(results) {
// Successfully retrieved the object.
if (results && results.length == "1") {
var playerObjectId = results[0].id
/// save only updated data to the local class ????
} else {
// no results, create a new local
console.log('no results')
// save as a new object
var gamers = new game();
gamers.set("somevalue", somevalue);
gamers.set("somevalue2", somevalue2);
gamers.save(null, {
success: function(gamers) {
// Execute any logic that should take place after the object is saved.
console.log('New object created with objectId: ' + gamers.id);
},
error: function(gamers, error) {
// Execute any logic that should take place if the save fails.
// error is a Parse.Error with an error code and message.
console.log('Failed to create new object, with error code: ' + error.message);
}
});
}
},
error: function(error) {
console.log("Error: " + error.code + " " + error.message);
}
});
} else {
console.log('array is empty, something went wrong')
//this array is empty
}

A create or update function has three distinct bits: find, update, or possibly create. Lets build the three separately.
function findGamerWithName(name) {
var game = Parse.Object.extend("Gamers");
query.equalTo("playername", name);
return query.find();
}
function updateGamer(gamer, someValue, someValue2) {
gamer.set("somevalue", someValue);
gamer.set("somevalue2", someValue2);
return gamer.save();
}
function createGamer(someValue, someValue2) {
var Gamer = Parse.Object.extend("Gamers");
var gamer = new Gamer();
return updateGamer(gamer, someValue, someValue2);
}
Now we can understand and test these separately (you should test them). And now, it's easy to write create or update logic...
function createOrUpdateGamer(name, someValue, someValue2) {
return findGamerWithName(name).then(function(gamer) {
return (gamer)? updateGamer(gamer, someValue, someValue2) : createGamer(someValue, someValue2);
});
}

Related

Parse.com - secure sending data with javascript SDK

I'm building right now simple game with Angular JS and Parse.com cloud as my database.
My goal is in the and of the game, to store user score inside Parse cloud.
But how can i do this securly, when anyone can get access to my Parse keys, becouse they are visible in my js file, and simply recreate Parse Object with some fake data, and then store it in my database ?
ACL's it's not the point in this particular case - right now i just turn of write acl before save, to prevent users from changing they scores before save.
In my game i don't have any Parse Users - i want to all peaople play my game, without logging in.
What do you think about idea to make 'fake' user like in first answer in this post ( becouse Anonymous anonymous can't be create in JS parse SDK ), and then track the session and the user ?
Is it even helpful in my case ?
Maybe i should make some check in Cloude Code - like comparison Cookies or local storage data before saving in Parse ( it will make cheating in game harder but still possible ) ?
Below i present my whole service to show you what is all about:
angular.module('Parsedb', [])
.provider('Parsedbmanager', function() {
this.$get = function($q, $http) {
// new Parse constructor
var ParseHighScore = Parse.Object.extend('ParseHighScore');
// create new obj
var parseHighScore = new ParseHighScore();
this.parseInit = function() {
Parse.initialize('myKey', 'myKey');
};
this.setParsedb = function(newScore) {
// set val
parseHighScore.set('HighScore', newScore);
// save score to cloud
parseHighScore.save(null, {
success: function (parseHighScore) {
// protect from change saved obj
var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(false);
parseHighScore.setACL(acl);
return parseHighScore.save();
},
error: function (parseHighScore, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
};
this.getParsedb = function() {
// need q to get this asynch
var deferred = $q.defer();
var query = new Parse.Query(ParseHighScore);
query.limit(5);
query.descending("HighScore");
query.find({
success: function(results) {
console.log("Successfully retrieved " + results.length + " scores.");
// resolve, if you have results
deferred.resolve(results);
},
error: function(error) {
deferred.reject(error.message);
}
});
return deferred.promise;
};
return this;
};
});
If you let the user to write to db then there will always be a situation where user can change data .. i think all you can do is, to abstract it from user

How Can I Update Another Object within User afterSave in Parse Cloud Code?

I have two classes - User, and GameScore. Each User has fields called [R,G,B,W,U], and when the user gets updated, I would like to update these fields for the matching user location in the GameScore class. I intend to do this by comparing city, country, and state fields, but would rather do it by matching LatLng (an array). I have tried accomplishing this with cloud code using afterSave:
Parse.Cloud.afterSave("User", function (request) {
var city = request.object.get("city");
var country = request.object.get("country");
var state = request.object.get("state");
var latLng = request.object.get("LatLng");
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.equalTo("city", city);
query.find({
success: function (results) {
// check if state and country match here, else create new record
var fromParse = JSON.parse(results);
var objectId = fromParse.objectId;
console.log(objectId);
// need to add the change in User's Score to globalScore for their city here
},
error: function (error) {
console.log("Error: " + error.code + " " + error.message);
var newScore = Parse.Object.extend("GameScore");
newScore.set("R",request.object.get("R"));
newScore.set("G",request.object.get("G"));
newScore.set("B",request.object.get("B"));
newScore.set("U",request.object.get("U"));
newScore.set("W",request.object.get("W"));
newScore.set("city",city);
newScore.set("country",country);
newScore.set("state",state);
newScore.set("LatLng",latLng);
newScore.save();
}
});
});
After deploying, and triggering the afterSave hook, I get this error:
E2015-11-26T17:13:57.896Z]v11 after_save triggered for User:
Input: {"object":{"B":0,"G":4,"LatLng":[47.6062095,-122.3320708],"R":6,"U":0,"W":3,"city":"Seattle","country":"US","createdAt":"2015-11-23T03:07:24.043Z","currentMonth":"Nov","number":"00000","objectId":"oC7RBcwztX","smsIds":[null],"state":"CA","textsLifetime":20,"textsThisMonth":20,"updatedAt":"2015-11-26T17:13:57.892Z"}}
Result: Uncaught SyntaxError: Unexpected end of input in :0
Parse.com! Help! Full disclosure: This is my first time using parse.com cloud-code. I'm enjoying it, but is there a way to clear the console log in parse's dashboard?
As per https://parse.com/docs/cloudcode/guide#cloud-code-aftersave-triggers:
You should be passing the object for User like:
Parse.Cloud.afterSave(Parse.User, function(request) {...}
Additionally, I don't believe you need to JSON.Parse the return data and id can be accessed with the 'id' property on parse objects.
gameScore.save(null, {
success: function(gameScore) {
// Execute any logic that should take place after the object is saved.
alert('New object created with objectId: ' + gameScore.id);
},
https://parse.com/docs/js/guide#objects-saving-objects

Get data from 2nd level pointer in Parse

I have a setup with Three relevant classes: _User, Article, and Profile. In Article I have a pointer named author for _User, and in Profile, I have the same; a pointer, but named user, for _User.
Now, I want to retrieve data from Article, with the cols firstname and lastname in Profile, where the pointer in Article matches the objectId in _User, and the pointer in Profile.
Basically what I would solve with an inner join in SQL.
How do I go about this with just one parse call?
This is what I have so far:
var Article = Parse.Object.extend("Article");
var query = new Parse.Query(Article);
query.include("category");
query.find({
success: function(results) {
console.log("Successfully retrieved " + results.length + " article(s):");
// Do something with the returned Parse.Object values
for (var i = 0; i < results.length; i++) {
var object = results[i];
console.log(object.get('title'));
console.log(object.get('content'));
console.log(object.get('category').get("categoryName"));
}
},
error: function(error) {
console.log("Error: " + error.code + " " + error.message);
}
});
Its a pleasure answering questions where the OP took the trouble to include a complete (and minimal) description of the data and the desired result.
I think I understand that you want to get Articles, and for each you want to get Profiles, and that the Profiles and articles are (logically) joined via a common pointer to User.
This can be done using an additional query per article. For clarity and maintainability, I like to break these things up into short, logical promise-returning functions, so...
// for handy array functions, like _.map, and turning var args into simple arrays
var _ = require('underscore');
// note: include underscore this way in cloud code (nodejs)
// for browser, see underscorejs.org to add to your project
// this will answer a promise that is fulfilled with an array of the form:
// [ { article:article_object, profile:profile_object }, {...}, ...]
function articlesWithProfiles() {
var query = new Parse.Query("Article");
query.include("category");
query.include("author");
return query.find().then(function(articles) {
var promises = _.map(articles, function(article) {
return profileForArticle(article);
});
return Parse.Promise.when(promises);
});
}
// return a promise that's fulfilled by associating the given article with it's profile
function profileForArticle(article) {
var author = article.get("author");
var query = new Parse.Query("Profile");
query.equalTo("user", author);
return query.first().then(function(profile) {
return { article:article, profile:profile };
});
}
// call it like this
articlesWithProfiles().then(function() {
// see edit below
var result = _.toArray(arguments);
console.log(JSON.stringify(result));
}, function(error) {
// handle error
console.log(JSON.stringify(error));
});

In Parse.com's Cloud code, asynchronous code is giving variables in for-loop the wrong value

I'm trying to save different food names without duplicates on parse.com. However, when I run the code, the database consists of the same 2 or 3 foods over and over, instead of 200 or so unique names.
Below is my function. I tried logging the name of the food at two different points, and I get different values. The first point gives the correct name of the food, but the second point only shows either flaxseed muffins or raspberry pie. I think the problem has to do with the code running asynchronously, but I'm not sure how to resolve the issue.
Parse.Cloud.define("recordFavorite", function(request, response) {
var foodList = request.params.foodList; //string array of food names
var Food = Parse.Object.extend("Food");
var query = new Parse.Query(Food);
for (i = 0; i < foodList.length; i++ ) {
var name = foodList[i];
console.log("before name is " + name);
var query = new Parse.Query(Food);
query.exists("name", name);
query.find({
success: function(results) {
if(results.length == 0){
var food = new Food();
food.set("name", name);
food.save(null, {
success: function(food) {
console.log("saved with name " + name);
},
error: function(food, error) {
}
});
} else {
//don't create new food
}
},
error: function(error) {
}
});
}
});
EDIT:
I was able to make some progress by modifying it to the code pasted below. Now it saves all the objects, including duplicates. I noticed that the lines
var query = new Parse.Query(Food);
query.exists("name", name);
returns an array of all the foods and doesn't filter out the objects containing "name". (To be clear, this was probably still occurring in the original code, but I hadn't noticed.)
Parse.Cloud.define("recordFavorite", function(request, response) {
var foodList = request.params.foodList; //string array of food names
var foodListCorrected = new Array();
var Food = Parse.Object.extend("Food");
// Wrap your logic in a function
function process_food(i) {
// Are we done?
if (i == foodList.length) {
Parse.Object.saveAll(foodListCorrected, {
success: function(foodListCorrected) {
},
error: function(foodListCorrected) {
}
});
return;
}
var name = foodList[i];
var query = new Parse.Query(Food);
query.exists("name", name);
query.find({
success: function(results) {
console.log(results.length);
if(results.length == 0){
//console.log("before " + foodListCorrected.length);
var food = new Food();
food.set("name", name);
foodListCorrected.push(food);
// console.log(foodListCorrected.length);
} else {
//don't create new food
}
process_food(i+1)
},
error: function(error) {
console.log("error");
}
});
}
// Go! Call the function with the first food.
process_food(0);
});
I think you're right about the problem being the async logic. The problem is that the outer loop completes as quickly as it can, firing off the various, slower async calls for your food lookup queries as it goes. The outer loop doesn't wait and because of what's know as 'variable hoisting' when you access 'name' inside your success function, its value will be the latest value of 'name' in the outer loop. So when the success function is called, the value of name has moved on to a different food to when you first initiated the exists/save query sequence.
Here's a really simple example:
Say your foodList looked like ['Muffin'], ['Cheesecake']. When you enter the loop for the first time, you have name='Muffin'. You fire off your exists query for name='Muffin' and that now happens asynchronously. Meanwhile, the outer loop happily moves on and sets name='Cheesecake' and fires off another exists query. Meanwhile. your first exists query completes and you are now ready to save the first food. But, because of hoisting, the value of name within your success function is now 'Cheesecake'. So it saves 'Cheesecake' when it should have saved 'Muffin' Then the second set of async queries complete, and this one also saves 'Cheesecake'. So you get two foods, representing your two unique foods, but both are called 'Cheesecake'!
Here's the classic article on variable hoisting, it is well worth a read:
http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
A way of solving this would be to only trigger the processing of the next food once all the async calls for the current food have completed. You can do this like this:
Parse.Cloud.define("recordFavorite", function(request, response) {
var foodList = request.params.foodList; //string array of food names
var Food = Parse.Object.extend("Food");
var query = new Parse.Query(Food);
// Wrap your logic in a function
function process_food(i) {
// Are we done?
if (i == foodList.length) return;
var name = foodList[i];
console.log("before name is " + name);
var query = new Parse.Query(Food);
query.exists("name", name);
query.find({
success: function(results) {
if(results.length == 0){
var food = new Food();
food.set("name", name);
food.save(null, {
success: function(food) {
console.log("saved with name " + name);
// Move onto the next food, only after all the async operations
// have completed.
process_food(i+1)
},
error: function(food, error) {
}
});
} else {
//don't create new food
}
},
error: function(error) {
}
});
}
// Go! Call the function with the first food.
process_food(0);
});
(Note, I've not tested this code, so there might be syntax errors).
I've not come across Parse before... I saw your question, went off to read about it, and thought it looked very interesting! I will remember it for my next PHP API project. I think there are some smarter things you can try to do. For example, your approach requires 2 async calls per food, one to see if it exists, and one to save it. For 200 foods, that's 400 async calls. However, the Parse API looks very helpful, and I think it will offer tools to help you cut this down. You could probably try something along the following lines:
You already have an array of strings of the names you want to save:
var foodList = request.params.foodList; //string array of food names
Say it looks like ["Cupcakes", "Muffins", "Cake"].
Now build a Parse query that gets all food names already on the server. (I don't know how to do this!). But you should get back an array, let's say ["Cupcakes", "Cheesecake"].
Now you an strip the duplicates in JavaScript. There'll be some nice questions here on StackOverflow to help with this! The result will be that "Cupcake" is a duplicate, so we are left with the array ["Muffins", "Cake"]
Now it looks like in Parse you can Batch some operations:
https://parse.com/docs/rest#objects-batch
so your goal is to save this array of ["Muffins", "Cake"] with one API call.
This approach will scale well with the number of foods, so even with 200 foods, you should be able to do it in one query, and one batch update per 50 foods (I think 50 is a batch limit, from the Parse docs), so at most you will need 5 API calls.
I believe this (https://www.parse.com/docs/js_guide#promises-series) is the solution you're looking for. You need to utilize promises to force synchronicity.

How to save 3 objects from node.js to parse.com with relations

I'm trying to use node.js (v0.10.28, mac os x) to save 3 objects to parse.com. The objects have relations to each other.
Here are the relations:
I configured the Classes using the data browser:
Car (not being updated by the script)
Drives
Res
StaSto
My goal is to understand Relations (yes, I read the documentation on relations on parse.com, but I can't apply it to this simple example). My script will
query for the car I want (only 1 in parse.com),
then it will create the objects for Car, Drives, Res and `StaSto
then it will relate the objects and put some fake values in them.
and finally, it will try to save them. Here is where the script fails with the error: Failed to create new myRes, with error code: undefined. myDrive gets created, even if it should be the last element to be created and the creation of myRes fails.
can anyone helps? Or is there other better way to do it?
Thanks.
Here is my script:
console.log("Starting");
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// PARSE Classes!
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var Parse = require('parse').Parse;
var parse_JSKey = "xxx";
var parse_AppKey = "xxx";
Parse.initialize(parse_AppKey, parse_JSKey);
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// PARSE Classes!
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var Drives = Parse.Object.extend("Drives");
var StaSto = Parse.Object.extend("StaSto");
var Res = Parse.Object.extend("Res");
var Car = Parse.Object.extend("Car");
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// fake data
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var myDriveData = {'distance':21, 'speed': 99};
var carID = "mrNQcPRNl4"; //already in parse.com!
var myStaSto_lat = 48.1333;
var myStaSto_lng = 11.5667;
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// insert into parse
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// --- query for the car
var carQuery = new Parse.Query(Car);
carQuery.get(carID, {
success: function(result) {
var thisCar = result;
// --- create a new drive
var myDrive = new Drives(); // created a new object, but empty
myDrive.set('distance', myDriveData.distance);
myDrive.set('speed', myDriveData.speed);
// --- create a new StaSto
var myStaSto = new StaSto();
myStaSto.set('coord',new Parse.GeoPoint({latitude: myStaSto_lat, longitude: myStaSto_lng}));
myStaSto.set('drive',myDrive);
// --- create a new Res
var myRes = new Res();
myRes.set('drive',myDrive);
myRes.set('car',thisCar);
myRes.set('res',717);
console.log(myRes);
// --- ----> save them all
myRes.save(null, {
success: function(response) {
// Execute any logic that should take place after the object is saved.
console.log('saved myRes succesfully');
myStaSto.save(null,{
success: function(response){
console.log('saved myStaSto succesfully');
myDrive.save(null,{
success: function(response){
console.log('saved myDrive succesfully');
},
error: function(response,error){
console.log('Failed to create new myDrive, with error code: ' + error.description);
}
}); // end of myDrive.save
},
error: function(response,error){
console.log('Failed to create new myStaSto, with error code: ' + error.description);
}
}); // end of myStaSto save
},
error: function(response, error) {
// Execute any logic that should take place if the save fails.
// error is a Parse.Error with an error code and description.
console.log('Failed to create new myRes, with error code: ' + error.description);
}
}); // end of myRes.save
},
error: function(object, error) {
console.log('error in car query: ' + error.code + " " + error.message);
}
}); // end of car query.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dont see anything wrong with your code around the 'save.res. You might want to read up on parse promises tho...
try playing with it in a less complex form.
try to save 'res' with just one FK type relation and , say a straight value in 'res.drive'..
In the lucky chance that you get a good Save instead of 'undef' error then...
you can try switching one of your current relation types to a pointer and see if that works.
It turned out that my code was correct. The error comes because I set up the column as a relation in parse.com (see pictures).
I changed that to Pointer, and voila, it works just fine. I must admit that the JavaScript developer Guide of parse.com is a bit short on mentioning this.

Categories