parse.com Storing a specific value once - ios

I'm new to the Parse api and am trying to figure out how to do the following:
My app stores strings ("foo", "bar", etc). "foo" should only exist once no matter how many times users attempt to add it - along with a count of how many times that string was attempted to be added. Thus if four users attempt to add the string "foo" I'd like the associated PFObject to look like:
name: "foo"
count: 4
(there should never be more than one object with a name of "foo")
I know what I could query for "foo", retrieve it, and then update the count but I have a feeling that a race condition would exist if multiple users are trying to update "foo" at the same time.
What is the right/best way to achieve this via the Parse API (I'm using iOS if it matters).

Parse cloud code beforeSave. https://parse.com/docs/cloud_code_guide#functions-modifysave
Before a save occurs, check for the existence of the row, and update it, then return response.error()
Something like this:
Parse.Cloud.beforeSave("MyObject", function(request, response) {
var newObject = request.object;
if(!newObject.existed()) {
var query = new Parse.Query("MyObject");
var name = newObject.get("name");
query.equalTo("name", name);
query.first().then(function(existing){
if(existing){
existing.increment("count");
return existing.save().then(function(){
response.error(name + " already exists");
});
}
else {
response.success();
}
});
}
else {
response.success();
}
});

Parse provides you with an incrementKey method that helps you avoiding problems when multiple users trying to update a counter. You could also decrement or increment by a given amount if needed. They call it Counters. But this only solves the race condition problem.
[myString incrementKey:#"count"];
[myString saveInBackground];
You can find more information here: https://www.parse.com/docs/ios_guide#objects-updating/iOS.

Related

Airtable Scripting block - Batch copy a field to another field in same table (10,000 records)

I'm trying to copy one field to another field in the same table with 10,000 + records, in batches of 50 using the Scripting App.
What am I doing wrong in this code block? It only copies the first record. If I remove the await, it'll copy 15 records then stop.
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
let records = query.records;
updateLotsOfRecords(records);
async function updateLotsOfRecords(records) {
let i = 0;
while (i < records.length) {
const recordBatch = records.slice(i, i + 50);
for (let record of recordBatch) {
let sourceValue = record.getCellValue('Merchant');
await table.updateRecordAsync(record, { 'LogoBase64': sourceValue });
}
i += 50;
}
}
you should use updateRecordsAsync function, not updateRecordAsync
When using single update function in loop, there is no sense to divide it into batches.
You exceed some limit of calls per second, that's why it stops.
For multiple updates, you need to use updateRecordsAsync, like this
while (recordsToWrite.length > 0) {
await updates.updateRecordsAsync(recordsToWrite.slice(0, 50));
recordsToWrite = recordsToWrite.slice(50);
}
Data that you should pass to it, more complex. I learned JS for 3 months and still have difficulties understandins all these "arrays of arrays of objects, passed via object's property". But that's the key to unerstand JS.
It's quite hard to leave basic/pascal habits, with plenty of inserted FOR loops, and GOTO sometimes))
I think, you already found the answer for 2 months, so my answer may be useless, but when i write it here, maybe i understand it better for myself. And help to some beginners also.
For single write, you pass (record, Object), where object is {field:'Value}
For multiple, you should pass
Array of Objects, where
Object is {id:recordID, fields:{object2}} , where
object2 is array of obj3 [ {obj3},{obj3}, {obj3} ], where
obj3 is a { 'Name or ID of field': fieldvalue }
you script might be:
let query = await view.selectRecordsAsync();
let updates=query.records.map(rec=>{
Map method can be applied for arrays, and 'query.records' is array of records. Here
'rec' is loop variable inside this "arrowfunction"
now let's create obj3 , in our case { 'Name or ID of field': fieldvalue }
{'LogoBase64':rec.getCellValue('Merchant')}
wrap it into fields property
fields:{'LogoBase64':rec.getCellValue('Merchant')}
and add record id
wrapping as Object.
To avoid complex string with linebreaks, and to make object creation easier, we can do it with function:
{rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
fuction myObj(rec){return {rec.id, fields:{'LogoBase64':rec.getCellValue('Merchant')}}
map(rec=>myObj(rec)) - can be written as map(myObj)
we need array of objects, and map method gets first array, doing something with each element and return other array, of results. like we need.
and now finally we get
let table = base.getTable('Merchants');
let view = table.getView('Grid view');
let query = await view.selectRecordsAsync();
function myObj(rec){return {'id':rec.id,'fields':{'Logobase64':rec.getCellValue('Merchant')}}};
let updates=query.records.map(myObj);
while (updates.length > 0) {
await table.updateRecordsAsync(updates.slice(0, 50));
updates = updates.slice(50); }

Populate List in Swift 2.0 and display results

I've been learning iOS development for the past three weeks, I'm currently following a course on Udemy so far so good.
However I'm following one of the lectures whereby we build an Instagram Clone.
The instructor is using three arrays which are as follows:
var usernames = [""] // Stores all usernames
var userIds = [""] // Stores all Id's of the given usernames
var isFollowing = [false] // Stores where or not you're following that user
To me trying to keep track of what userId goes with what username using two arrays is basically an accident waiting to happen so I decided to set off and find a more feasible approach. I reverted back to my .Net days and decided to create a list so I went and created a class as follows:
class Users{
var Username : NSString = ""
var UserId : NSString = ""
var Following : Bool = false
}
Now inside my ViewController I make a call to Parse which returns me a list of users and I'm basically trying to loop through the response, and add them to the list class as shown here:
var t = [Users]() // After googling the web, this seems to be the syntax for a list declaration ?
let u = Users()
for object in users{
if let o = object as? PFUser {
u.Username = o.username!
u.UserId = o.objectId!
u.Following = o.IsFollowing!
self.t.append(u)
}
}
print(self.t)
Now when I print this to the console I see the following:
ParseStarterProject_Swift.Users
As I have one user at present, however when I try to loop through T and display the username in the console it doesn't display anything.
for x in t {
print(x.Username)
}
Your basic intuition is correct, it's better to have an array of custom objects, not multiple arrays.
Regarding making it more Swifty, consider your Users type. You might want something like:
struct User {
let username: String
let userId: String
let following: Bool
}
Note,
property names should start with lowercase letter;
Users should probably be called User, as it represents a single user;
we don't generally initialize values to default values like that, but rather specify them in the initializer;
we probably use String not NSString;
if a property cannot change, you'd use let, not var;
properties begin with lower case letters;
Then you can do something like:
var t = [User]()
for object in users {
if let o = object as? PFUser {
t.append(User(username: o.username!, userId: o.objectId!, following: o.IsFollowing!)
}
}
print(t)
Clearly, with all of those ! forced unwrapping operators, you'd want to be confident that those fields were populated for all of those properties.
Using struct is nice because (a) it's a value type; (b) you get the initializer for free; and (c) you can just print them. If you really wanted User to be a reference type (a class), you'd do something like:
class User {
let username: String
let userId: String
let following: Bool
init(username: String, userId: String, following: Bool) {
self.username = username
self.userId = userId
self.following = following
}
}
And if you wanted to be able to just print them, you'd define it to conform to CustomStringConvertible:
extension User: CustomStringConvertible {
var description: String { return "<User; username = \(username); userId = \(userId); following = \(following)>" }
}
With the class, you can feel free to change that description computed property to show it in whatever format you want, but it illustrates the idea.
You are correct in considering that keeping track of what userId goes with what username using two arrays is dangerous, you in the correct direction with your approach.
First, I would just like to suggest that you use correct naming convention:
Classes should be singular (except in very specific cases).
Variable/property names should begin with lowercase.
This would mean that your user class should look like this:
class User {
var username : NSString = ""
var userId : NSString = ""
var following : Bool = false
}
I will keep your existing naming use for the next part. The main problem with your code is that the variable "u" is a object which you create only once and then modify it. You should be creating a new "Users" object for each user instead of modifying the original. If you don't do this you will just have an array with the same user multiple times. This is how your code would look now:
var t = [Users]()
for object in users {
if let o = object as? PFUser {
let u = Users()
u.Username = o.username!
u.UserId = o.objectId!
u.Following = o.IsFollowing!
self.t.append(u)
}
}
print(self.t)
Next you mention that when you print to console you see the text: ParseStarterProject_Swift.Users, that is because Swift does not automatically print a pretty text with the content of your object. In order for it to print something more detailed, your "Users" object would need to implement the CustomStringConvertible. You can see a more detailed answer about that here: how-can-i-change-the-textual-representation-displayed-for-a-type-in-swif.
Lastly, you mention that when you loop trough "t" and display the username in the console it does not display anything. This is caused by one of two things:
Because there are no users being returned from parse, so the "t" array is actually empty. Try print(t.count) to see how many objects are in the array.
Because your "Users" object declares an empty string "" as the default username and the username is not being set correctly when getting the data from the parse. Which means that it IS actually printing something, just that it is an empty string. Try defining a different default value like var username : NSString = "Undefined" to see if it prints something.
Good luck learning swift!

Best practice to retrieve list of friends information. How to detect multiple queries are finished?

I have users structure lke this:
{
"users": {
"uniqueID1": {
"name": "Anon",
"friends": {
"uniqueID2": true,
"uniqueID3": true
}
}
"uniqueID2": { },
"uniqueID3": { },
}
}
I want to show a user's friends' names. I have to access $user/friends/ to get list of unique IDs, and iterate the list to get friend's information. But iterating the unique ID is making multiple queries, and I have to always check if all of my queries are finished. According to the doc, it seems multiple queries will not impact the performance too much, but if I want to update my view only when all of the queries are finished, I have to check how many queries are finished.
Is there no way of 'execute a completion block when all queries are finished'?
Pseudocode
var totalNumOfFriends = 0
var tempArray = NewArray()
ref(/users/uniqueID1/friends).observeEventType{ snapshot
var uIDList = snapshot.children's keys
totalNumOfFriends = uIDList .count
for uID in uIDList {
var nameRef = ref(/users/uID/name) i.e. /users/uniqueID3/name
nameRef.observeSingleEventOfType { snapshot
var username = snapshot.value
tempArray.append(username)
if tempArray.count == totalNumOfFriends {
// If counts are the same, tempArray has all of my friends' names
// Now update view using tempArray
}
}
}
}
Pseudocode explanation:
Get list of unique IDs from /users/uniqueID1/friends
'Save' number of unique IDs. (Explained in step 4)
For each unique IDs from the list, get user's name by using ref like this /users/uniquedID2/name
For each name retrieved, add it to temporary array. Once the count of the temporary array equals to the count from step 2, update my view as I have retrieved all the names.
Firebase has no built-in way to signal when a number of queries has finished. But you can easily implement this in your own code. Your approach with a counter that checks how many items have already been loaded is the most common approach for that.

How can i count the number of followers using parse saved data as shown:

i have saved my followers list as shown in the table image
in the "user" column , i've saved the objectId of user being followed and in the "follower" column i've saved the currentUser (follower)
now i want to get the number of followers of each user.. how can i do that?
Parse Query for counting objects
https://parse.com/docs/ios/guide#queries-counting-objects
Where you can execute 1 query to get followers count of 1 user. Which can easily max out parse api limit i.e. (counting object query 160 requests per minute). For this Parse and Me, both not recommend you to use counting Objects especially if you expect significant number of users.
Parse Recommendation to avoid Count Operations
https://parse.com/docs/ios/guide#performance-avoid-count-operations
You should use parse cloud code(https://parse.com/docs/ios/guide#cloud-code) and have a key in your User table which can keep record of current followers count for that user.
Cloud code in your case.
Parse.Cloud.afterSave("Followers", function(request) {
if(request.object.existed() == true)
// No need to increment count for update due to some reason
return;
});
// Get the user id for User
var userID = request.object.get("user");// Or request.object.get("user").id;
// Query the user in actual User Table
var UserQuery = Parse.Object.extend("User");
var query = new Parse.Query(UserQuery);
query.get(userID).then(function(user) {
// Increment the followersCount field on the User object
user.increment("followersCount");
user.save();
}, function(error) {
throw "Got an error " + error.code + " : " + error.message;
});
});
Unfollowing might also happen, Leaving After Delete practise to you
https://parse.com/docs/ios/guide#cloud-code-afterdelete-triggers
You can use the Parse Server countObjectsInBackground() function for achieve your task. It worked for me. I have used iOS SDK version 1.17.3.
Here is the Swift 4 sample code:
let query = PFQuery(className: "yourTableName")
query.whereKey("user", equalTo: "yourUserId")
query.countObjectsInBackground { (count: Int32, error: Error?) in
if error == nil {
print("My followers number: \(count)")
}
For more information: https://docs.parseplatform.org/ios/guide/#counting-objects
Hope it works.

Data disappearing from the database?

I am running a grails application and I am receiving the weirdest error I've probably ever encountered. One "field" in a model got data that just disappears for no reason.
I have two Model or a Domain class in my project with the following set up:
class Insertion {
String title
Date insertDate
static hasMany = Dataholder
static constraints = {
title(unique: true)
}
}
class Dataholder {
String product
int somenumber
int somenumber2
int somenumber3
Date startDate
Date endDate
List<String> somedatalist
Insertion insertions
static belongsTo = Insertion
static constraints = {
}
}
The "insertion" class is representing every time a user might input a bunch of dataholders. The dataholder represents all the data for that specific product. Important to know is that the data that disappears is contained in the Dataholder model and the ONLY data that disappears is the somedatalist.
This is the magic which is completely confusing, when I insert the data and saves it. It all goes well:
if (!errors) {
dataholderValidator.each {
it.insertion = insertion
it.save()
}
def results = Dataholder.findAllByInsertion(insertion)
I do some validating and apply data to every Dataholder and then if everything goes well, if(!errors) I add the insertion to each object. After that is done I save each objec, saving the data to the database. You may think it's going wrong here but just wait and be amazed.
After saving I get all the Dataholders from the database (since I want to be sure that the data was saved before printing it out to the user) by using the insertion. This is where the strange part begin, what I get back is the correct data:
results.each {
it.somedatalist.each { it2 ->
if(!weekdays.contains(it2))
weekdays.add(it2)
}
}
Populate an array with all the unique items from the datalist. Then printing it out to the view and voila:
Now, we just wait for the users confirm of all the data in the view and when he or she is clicking on a confirm button the insertion title is sent with post to function which would retrieve the data and to my surprise the somedatalist is null.
This is the functionality that retrieves the data:
def result = Insertion.findByTitle(insertionTitle)
def results = Dataholder.findAllByInsertions(result)
When putting a breaking point after results I can for sure confirm that every Dataholder contains the correct the right data except that somedatalist which is equal to null.
I've tried to get the data above by using the Insertion Title instead of just using the object and it works well. I can't understand why the data is populated in the database in one second and how something can just disappear?
Test:
void testSaveDataholder() {
Insertions insertion = new Insertion(title: 'adadad', insertDate: new Date())
insertion.save()
assert Insertion.all.size() == 1
Dataholder ed = new Dataholder(product: 'abc123', somenumber: 123, somenumber2: 13, startDate: new Date(), endDate: new Date(), somedatalist: ['TI'], insertions: insertion)
ed.save()
assert Dataholder.all.size() == 1
assert Dataholder.all.first().somedatalist.size() == 1
assert Dataholder.all.first().insertions.title == 'adadad'
assert Insertion.findAllByTitle('adadad').size() == 1
assert Dataholder.findAllByInsertions(Insertion.all.first()).size() == 1
}
This test all returns true, I am using Grails 2.1.
EDIT: I am using the in-memory database with "update" as configuration. So I can't really view the data. But it should be there.
Please help me with your sage advice and better wisdom.
It just has come to my mind. Persisting a collection of objects into single column breaks the 1st normal form, so it is not the correct way to do it. I have immediately googled an issue in JIRA:
http://jira.grails.org/browse/GRAILS-1023
The correct way is to create a new domain class with single String attibute and use standard one-to-many relation.

Resources