Better way to retrieve nested array in a PFQuery in Parse.com? - objective-c-blocks

I am using Parse.com to store data for an iOS app. The code below successfully retrieves all values in a nested array belonging to a PFObject "game". However, if I need to query for another array (at the same level as "winners" (say "losers") i cannot get it to work, and not all the values in the array losers gets populated. I suppose i could do them all on the main thread and not try to nest the fetches (nested blocks) but i'm wondering if:
1) Is the way i'm storing my data prohibiting me from using Parse's built in query/fetch functionality properly? Data stored as:
PFObject * newGame = [PFObject objectWithClassName:#"Game"];
NSArray * winner = [NSArray arrayWithObjects:[_allPlayersPFDictionary objectForKey:[playerData objectAtIndex:0]], [playerData objectAtIndex:1], nil];
[_gamePF addObject:winner forKey:#"winners"];
2) Is there a better, cleaner way to do the query and get ALL the values of all nested arrays of data in a query? Again, winners is not a PFObject, but is an array of array of PFObject of 2 different types ([PFObject fetchAll:(NSArray *)winnersArray] does not work, because all objects in the Array must be of the same 'type' of PFObject). I store it this way because each winning player has another PFObject (1 to many) "powers" associated with them.
Here is the query that works but i can't figure out how to add "losers" to it and properly populate all data in the background.
PFQuery * gamesQuery = [PFQuery queryWithClassName:#"Game"];
[gamesQuery orderByDescending:#"createdAt"];
gamesQuery.limit = 30;
[gamesQuery findObjectsInBackgroundWithBlock:^(NSArray * theGames, NSError * error) {
if (error) {
NSLog(#"ERROR: There was an error with the Query to get Games!");
} else {
for (PFObject * aGame in theGames) {
for (NSArray * aWinner in [aGame objectForKey:#"winners"]) {
[[aWinner objectAtIndex:0] fetchIfNeededInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (error) {
NSLog(#"ERROR: There was an error with the Query to get Player in winnersArray!");
} else {
[PFObject fetchAllIfNeededInBackground:[aWinner objectAtIndex:1] block:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"ERROR: There was an error with the Query to get Powers in winnersArray!");
} else {
[_gamesPF addObject:aGame];
NSLog(#"Games from viewDidLoad %#", _gamesPF);
[_tableView reloadData];
}
}];
}
}];
}
}
}
}];

Well... i feel kinda stupid. Definitely much easier to use Parse in an object oriented manner for the data model. Was able to easily solve it by remodeling the data to be:
Game (PFObject *) has:
--> winners { (PFObject *), (PFObject *), ..., nil }
--> losers { (PFObject *), (PFObject *), ..., nil }
where a winner is created as:
[testWinner1 addObject:power1 forKey:#"power"];
[testWinner1 addObject:power2 forKey:#"power"];
[testWinner1 addObject:[_playerPFDictionary objectForKey:#"Tom"] forKey:#"player"];
Which then makes the query much easier and involves only one background block like so:
PFQuery * gameQuery = [PFQuery queryWithClassName:#"TestGame"];
[gameQuery includeKey:#"winners.player"];
[gameQuery includeKey:#"winners.power"];
[gameQuery includeKey:#"losers.player"];
[gameQuery includeKey:#"losers.power"];
[gameQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"failed");
} else {
NSLog(#"testGame: %#", [objects objectAtIndex:0]);
}
}];

Related

What is causing this logic error?

Here is my code and here is the output... I do not understand why my if statement above the log is allowing this to happen...This if statement that is nested inside of the for loop should not allow output whenever the fullname is the same the description...
Firebase *firebase = [[Firebase alloc] initWithUrl:[NSString stringWithFormat:#"%#/Recent", FIREBASE]];
FQuery *query = [[firebase queryOrderedByChild:#"groupId"] queryEqualToValue:groupId];
[query observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
//Is group
if ([snapshot.value[#"type"] isEqual: #"group"]){
self.title = snapshot.value[#"description"];
}
//Is individual
else{
NSString *senderId = snapshot.value[#"userId"];
PFQuery *query = [PFQuery queryWithClassName:PF_USER_CLASS_NAME];
[query whereKey:#"objectId" equalTo:senderId];
query.limit = 1;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// Do something with the found objects
for (PFObject *object in objects) {
NSString *userName = [[PFUser currentUser]fullname];
if (object[#"fullname"] != userName){
self.title = object[#"fullname"];
NSLog(#"You're talking to: %#", object[#"fullname"]);
NSLog(#"Logged in user: %#", userName);
}
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
}];
Heres the log as well
2016-03-12 18:48:09.844 Gibr[34128:1436940] You're talking to: Testerten
2016-03-12 18:48:09.844 Gibr[34128:1436940] Logged in user: Travis Tubbs
2016-03-12 18:48:09.845 Gibr[34128:1436940] You're talking to: Travis Tubbs
2016-03-12 18:48:09.845 Gibr[34128:1436940] Logged in user: Travis Tubbs
The problem that you are using != to compare two strings. But that's not what it's for; it's for comparing objects. The two string variables are not the same object; they are two different objects, two different variables.
If you want to know whether two string variables have the same value as strings, use isEqualToString:.
if (![object[#"fullname"] isEqualToString: userName])

Parse: Query to change labels text

I can currently query a parse class, but can't figure out how to change a labels text if the returned values match the query. I am relatively new to objective C and Parse so my knowledge on the subject is little. My query looks like this (with the text of what i'm trying to achieve underneath).
PFQuery *FTQ0 = [PFQuery queryWithClassName:#"Class1"];
[FTQ0 whereKey:#"Location" equalTo:#"The Shop"];
//Label.text = query (object?)
Thanks in advance.
Here is a solution. You have to be careful though, because there might be many objects that have key Location and equal to The Shop. This is why parse is returning an array of objects. In this case, I pick the first object in the array and display it.
PFQuery *query = [PFQuery queryWithClassName:#"Class1"];
[query whereKey:#"Location" equalTo:#"The Shop"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
Label.text = [NSString stringWithFormat:#"%#", [[objects firstObject] objectForKey:#"WHATEVER YOU WANT TO DISPLAY EX. NAME, LOCATION..."]]
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
For more information please visit https://parse.com/docs/ios/guide
Something like this :
PFQuery *query = [PFQuery queryWithClassName:#"Class1"];
[query whereKey:#"Location" equalTo:#"The Shop"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error && object) {
// Do your stuff
Label.text = [[object objectForKey:#"YOUR KEY"] stringValue];
} else {
// Error or null object
}
}];
In this example the query return only the first object.
Thanks so much for your quick and helpful response! This was an issue that was troubling me allot! The code provided works!
Thanks!

Get int value out of pfquery to change settings objective-c

Im trying to work with a script thats in objective c i want to make an int i can access throughout view did load to change settings or the viewcontroller but it doesn't work like swift and i get a null out of the pfquery so i tried to make an array and couldn't get anything out of the query with that as well
what is the best way to set up and int variable and set it in the pfquery then use if statements later on
here is some of my code
__block int setting = 0;
PFQuery *query = [PFQuery queryWithClassName:#"test"];
[query whereKey:#"testActive" equalTo:[NSNumber numberWithBool:YES]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %lu scores.", (unsigned long)objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
setting = [[object objectForKey:#"testSetting"] intValue];
// When checked in the loop i get all the right data
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
// When checked here i get settings = 0 when it should be 1
if (setting == 0) {
self.segmentedControl.selectedSegmentIndex = 0;
} else if (setting == 1) {
self.segmentedControl.selectedSegmentIndex = 1;
}
if (setting == 3) {
[self.segmentedControl setEnabled:YES];
}else {
[self.segmentedControl setEnabled:NO];
}
But when i check the setting int its always 0 but if i log in the loop is gives me the correct number
thanks for your help
Thanks for your help but i figured it out..
findObjectsInBackgroundWithBlock was not completing before i did the if statements
so i moved the code into a new function which i ran when the PFQuery was completed giving me the correct int

A query based on 2 other query results

EDIT 3: Okay forget the previous edits, I finally figured out it all out! But I still have one small question. The query finds and displays the username of the users who share the same Favourite Foods, and then displays their entire Favourite Food array. My question is, how can I display only the favourite foods that are the same as the current users favourite foods? Code:
PFQuery *query = [PFQuery queryWithClassName:#"_User"];
[query whereKey:#"username" notEqualTo:[PFUser currentUser][#"username"]];
[query whereKey:#"favouriteFood" containedIn:[PFUser currentUser][#"favouriteFood"]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
NSString *username = object[#"username"];
NSArray *results = object[#"favouriteFood"];
NSLog(#"Username: %# Shared Favourite Food: %#", username, results);
}
}
else {
NSLog(#"O dang");
}
}];
This displays the user who has atleast 1 common favourite food with the current user, but then displays all of their favourite food, instead of the ones that are common.
Thanks.
First Post:
I'm trying to create a query that gets the result of query1 (an array) for the current user, and then uses query2 to search the database for other accounts that contain one or more of the same data in their array. Here is an example to help understand what I'm trying to do:
Users have the ability to enter data into an array, let's call the array favouriteFoodArray. All data entered into the array is saved to Parse in a row called favouriteFood.
Now, the current user wants to search for other users who share the same favourite food (at least one array entry the same).
So I have query1 to find the current User's favourite food array:
PFUser *currentUser = [PFUser currentUser];
PFQuery *favouriteFood = [PFQuery queryWithClassName:#"_User"];
[favouriteFood whereKey:#"username" equalTo:currentUser.username];
[favouriteFood findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFUser *object in objects) {
self.usernameString = object.username;
[self.results arrayByAddingObjectsFromArray:object[#"favouriteFood"]];
NSLog(#"Username: %#, Favourite Food: %#", self.usernameString, self.results);
}
}
else {
NSLog(#"Error..");
}
}];
query2 then goes and finds the favourite food of ALL the users, so we can compare the query1 result with the query2 result and display the usernames of the people that have at least one common array entry:
PFQuery *allFavouriteFood = [PFQuery queryWithClassName:#"_User"];
[allFavouriteFood findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFUser *object in objects) {
self.allUsernameString = object.username;
[self.allFavouriteFoodArray arrayByAddingObjectsFromArray:object[#"favouriteFood"]];
NSLog(#"Username: %# Favourite Food: %#", self.allUsernameString, self.allFavouriteFoodArray);
}
}
}];
Now, these 2 queries work, the problem I am having is creating a third query to compare the query1 result with the query2 result. I've tried using:
...
[finalSearch whereKey:#"favouriteFood" containedIn:self.allFavouriteFoodArray];
...
but I get some strange errors. I've been trying to work this out aaaallll day. Any help is greatly appreciated. I hope I haven't over-complicated all of this.
Thank you.
EDIT:
Here's an example for query3:
PFQuery *results = [PFUser query];
[results whereKey:#"favouriteFood" containedIn:self.allFavouriteFoods];
[results findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFUser *object in objects) {
self.allUsernameString = object.username;
[self.favouriteFoodResults arrayByAddingObjectsFromArray:object[#"favouriteFood"]];
NSLog(#"Username: %# Shared Favourite Food: %#", self.otherUsernameString, self.allFavouriteFoodArray);
}
}
else {
NSLog(#"Error");
}
}];
I get all sorts of errors when I have whereKey:containIn: self.allFavouriteFoods.
'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: $in)'.
Your exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: $in)' means that you haven't actually got a list of food to look for when you make the request. You should call fetchIfNeededInBackgroundWithBlock: on the current user to ensure you have the food list (allFavouriteFoods) available.
Once you have that, i.e. in the completion block, your search for users with any matching foods is correct:
[results whereKey:#"favouriteFood" containedIn:self.allFavouriteFoods];
But you can reference it directly as:
[results whereKey:#"favouriteFood" containedIn:[PFUser currentUser][#"favouriteFood"]];
(assuming that the current user will always have some favourite food when this is run)

Fetch pointer key if needed

I have a PFObject and a few of the keys contain pointers. Sometimes I'm not including these in the original query. How do I fetch these for an existing object? Do I have to form a whole new PFQuery?
The accepted answer is incorrect. The OP said that sometimes the related objects are not included in the original query, and asks if another query is necessary.
includeKey is for use in the original query. If the objects are not included, the correct approach is to use fetchIfNeeded:
https://parse.com/docs/ios/api/Classes/PFObject.html#//api/name/fetchIfNeeded
PFObject *department = user[#"department"];
// If includeKey was not used for department in the original query, department is now only a stub pointing to the actual object.
[department fetchIfNeededInBackgroundWithBlock:^(PFObject *object, NSError *error) {
NSString *departmentName = department[#"name"];
}];
Use includeKey: on any pointer keys you want included in the query.
PFQuery * query = [PFQuery queryWithClassName:#"SomeParseClass"];
[query includeKey:#"SomePointerKey"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
PFObject * firstOb = objects[0];
PFObject * pointerObject = firstOb[#"SomePointerKey"];
}
}];
If you already have an object, you can fetch the pointer object. If it doesn't have a reference to the object, you would have to stack fetches:
if (ob[#"SomePointerKey"]) {
// ob already has pointer to object
PFObject * pointerOb = ob[#"SomePointerKey"];
[pointerOb fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
// pointerOb ready here!
}
}];
}
else {
// ob is missing pointer data, fetch it!
[ob fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
PFObject * pointerOb = object[#"SomePointerKey"];
[pointerOb fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
// pointerOb ready here!
}
}];
}];
}