Handle one to many relation in Parse in iOS - ios

I have this scenario:
One post could have many comments. So I create a Post class and a Comment class in Parse.com. Here are the definitions or the class and their data:
One post:
The post have two comments:
I want to retrieve the post with the first comment from a specific author. Here is my code:
PFQuery *query = [PFQuery queryWithClassName:#"Post"];
[query orderByAscending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *posts, NSError *error) {
for (PFObject* obj in posts) {
PFRelation* comments = [obj objectForKey:#"comment"];
PFQuery* theQuery = [comments query];
[theQuery whereKey:#"author" equalTo:#"John"];
[theQuery getFirstObjectInBackgroundWithBlock:^(PFObject *comment, NSError *error) {
NSLog(#"Post title=%#,body=%#", [obj objectForKey:#"title" ],[obj objectForKey:#"body"]);
NSLog(#"Comment content=%#",[comment objectForKey:#"content"]);
}];
}
}];
I don't think it's efficient although it works. And it's hard to tell when the queries are finished because there are two nested asynchronized calls.
Does anybody have better solution? Thanks.
EDIT:
The reason I think it's not efficient is because there are nested queries. But I don't know how to get what I want by using Relation. Maybe I should not use Relation? Instead, I should assign ObjectId of Post to Comment class? (But this method is not as easy as Relation in inputing data)

Relation type is recommended for many to many while for one to many "pointer" and "array" are recommended

Related

Parse.com Following / Follower Logic

Having some trouble trying to figure out some logic for the following situation.
I have built a friends system for my mobile app using parse. Simply put, when a user "follows" something they are put into a relationship. That relationship contains all of the people that the individual user has folowed.
User
Relationship - Friends (contains all of the users that that overall user has followed)
I can query who an individual user is following fairly easily:
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:#"asg"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for (PFObject *object in objects) {
PFRelation *friendsRelation = [object objectForKey:#"Friends"];
PFQuery *query = [friendsRelation query];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSLog(#"%#", objects);
} else {
}
}];
}
}];
How would I query who is following a certain user, though? So, a users followers.
If you already know the user, use whereKey:equalTo::
PFObject *user = ...;
PFQuery *query = [PFUser query];
[query whereKey:#"Friends" equalTo:user];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
If you need to do a query to get the user then instead combine the requests with whereKey:matchesQuery::
[query whereKey:#"Friends" matchesQuery:userQuery];
I actually did a bunch of blog posts talking about implementing a friends list that might help you:
Overview
JavaScript part 1
JavaScript part 2
JavaScript part 3
The code samples are in JavaScript, but shouldn't be too hard to convert to iOS.
It covers the relationships, querying, updating etc.
The short answer to your question it to use a Join Table instead of a Relation so you can easily query from both sides of the relationship.
You can find a lot of information in the relations guide on the Parse site:
https://parse.com/docs/relations_guide#manytomany-jointables
I think what you are asking for is the PFUser *user = [PFUser currentUser] method. If you are trying to retrieve friends for the current user logged in. You could use the same for retrieving the users that the current user logged in has followed.

iOS Parse SDK: How to count elements from one-to-many relationship

Description = I have a project that allows users to post things. These posts will be displayed in a tableView, (the comments will not be shown in this tableView). When a user presses a post, they will segue to a screen where they can post a comment. In the tableViewCell there will be a label like "(0) comments" to display how many comments belong to the post.
2 Queries happen. One query to populate the tableView, and another to query the amount of comments from each post at each indexPath.
Desired Result = If there are 5 comments on a post, to be able to return how many comments where made on each post so I can .count them into a string to say, "(5) Comments".
Problem = I'm not sure how to query for the results of a query, or if that is even what is supposed to happen. I'm not even sure if this is going right, please help someone who is knowledgable. I attached what I have done (not a copy and paste i typed all of that [so if something isn't right or there was a typo, please do not respond telling me that that was the problem with the code]).
What I have done =
-viewDidLoad...
PFQuery *postQuery = [PFQuery queryWithClassName:#"Post"];
[userQuery whereKey:#"postedByID" equalTo:selectedUserID];
[userQuery orderByDescending:#"createdAt"];
[userQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
tableArray = objects;
[self.tableView reloadData];
}];
-cellForRowAtIndexPath...
PFObject *post = [tableArray objectAtIndex:indexPath.row];
PFFile *imgFile = post[#"postPicture"];
[imgFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
cell.picture.image = [UIImage imageWithData:data];
}];
NSString *dateAndTime = [NSString stringWithFormat:#"%# %#", post[#"postDate"],post[#"postTime"]];
cell.categoryLabel.text = post[#"category"];
cell.subCategoryLabel.text = post[#"subCategory"];
cell.dateLabel.text = dateAndTime;
cell.descriptionLabel.text = post[#"description"]];
//below is where my headaches are coming from
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
//WHAT DO I DO HERE? (this probably isn't right, but I feel like it should be)
[commentsQuery whereKey:#"parentID" containedIn:[tableArray objectAtIndexPath:indexPath.row][#"objectId"];
[commentsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSLog(#"%#", objects);
}];
This returns an empty log.
I know for a fact that there are multiple comments in relation to some of the posts, yet still returns nothing. I believe this is because my commentsQuery isn't right, or something. I dunno, please help someone.
If I understand correctly your case, you should try with:
[commentsQuery whereKey:#"parentID" equalTo:[tableArray objectAtIndexPath:indexPath.row][#"objectId"];
since you are querying all of the comments belonging to a specific post.
containedIn would allow you to get all of the comments belonging to a given set of posts.
You should consider changing your design, as counting the comments for every post is extremely inefficient.
Take a look at the documentation on Cloud Code that runs after save. It specifically talks about a comments counter on a Post class, then you get the count as a simple property when you query the Post.
It is OK to make writes (that are less frequent) a little more expensive, to make reads much cheaper.

unable to join two or more class in parse

I have used this code ... i have two class named as addcomment and addpost and a key facebookId available in both. Now i want to get data from both table where facebookId is matched
Thanks in advance.....
PFQuery *innerQuery = [PFQuery queryWithClassName:#"addcomment"];
[innerQuery whereKeyExists:#"facebookId"];
PFQuery *query = [PFQuery queryWithClassName:#"addpost"];
[query whereKey:#"facebookId" matchesQuery:innerQuery];
[query includeKey:#"addcomment"];
[query findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
NSLog(#"comments array data is :%#",comments);
// comments now contains the comments for posts with images
}];
You cannot get objects from two tables unless one has a relation (or pointer) to the other. If all they have in common is the facebookId, then you will only get objects from addcomment or addpost.
It seems to me that comments would belong to a post(?). If you include a pointer between them, you can get objects from both classes using includeKey.

JOIN TABLES parse.com

I have two parse tables POST and COMMENT.
I want to get all post where there are new comments. I am making checks based on viewedDate field in post table and createdAt field in Comment Table.
SELECT *
FROM POST p
INNER JOIN COMMENT c
ON p.objectId==c.pId and c.createdAt > p.viewedDate;
PFQuery *post = [ParsePost query];
[post findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
PFQuery *post = [ParsePost query];
PFQuery *comment = [ParseComment query];
//how add greater than for keys from other table?
//line below crashes
[comment whereKey:#"createdAt" greaterThan:[objects valueForKey:#"viewedDate"]];
[comment whereKey:#"post" matchesKey:#"objectId" inQuery:post];
[post whereKey:#"objectId" matchesKey:#"post" inQuery:comment];
[post findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
}];
}];
UPDATE
*This is how i do it currently*
PFQuery *postView = [ParsePost query];
[postView whereKey:#"author" equalTo:[ParseLigoUser currentUser]];
[postView findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for (ParsePost *post in objects) {
PFQuery *comment = [ParseComment query];
[comment orderByDescending:#"createdAt"];
if (post.viewedDate) {
[comment whereKey:#"createdAt" greaterThan:post.viewedDate];
}else {
continue;
}
[comment whereKey:#"post" equalTo:post];
[comment countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (number>0) {
if (!self.newposts) {
self.newposts = [NSMutableDictionary dictionary];
}
[self.newposts setObject:#"NOTSEEN" forKey:[post.objectId stringByAppendingString:#"Comment"]];
self.postCount += 1;
}
}];
}
}];
UPDATE 2 with relations
PFQuery *postView = [ParsePost query];
[postView whereKey:#"author" equalTo:[ParseLigoUser currentUser]];
[postView setLimit:100];
[postView findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for (ParsePost *post in objects) {
PFRelation *rel = [post relationForKey:#"hasComment"];
PFQuery *query = [rel query];
[query orderByDescending:#"createdAt"];
if (post.viewedDate) {
[query whereKey:#"createdAt" greaterThan:post.viewedDate];
}else {
continue;
}
[[rel query] countObjectsInBackgroundWithBlock:^(int number, NSError *error) {
if (number>0) {
if (!self.newposts) {
self.newposts = [NSMutableDictionary dictionary];
}
[self.newposts setObject:#"NOTSEEN" forKey:[post.objectId stringByAppendingString:#"Comment"]];
self.postCount += 1;
}
}];
}
}];
Your problem isn't really the lack of join table queries on parse, but rather that you designed your database with a sql mindset instead of a NoSQL mindset (which of course is common for all who comes from the relation db world).
When designing your database "schema" for parse (or any other NoSQL database) you need to free yourself from thinking in relations and normalization etc to thinking in data access. How will you access your data? Start by thinking on how you will query your data, and then design your db to optimize for these queries. The most important for your mobile app is to minimize the number of connections to a remote server, and to minimize client side handling.
There are several ways to fix solve your current problem. You can, of course, create some queries that will work around the lack of join table queries, and handle some of this on the client. That could probably work short-term if this feature needs to be implemented quickly.
A long-term approach would be to redesign your schema to meet your requirement of easily retrieving posts that have new comments.
One solution (of several possible):
Create a new property in the Post class: “newcomments” that is a boolean.
Create a cloud code snippet that updates the newcomments property in Post whenever a new comment is created (sets it to TRUE).
This snippet should be run in the afterSave hook for Comment (https://parse.com/docs/cloud_code_guide#functions-aftersave)
Then, whenever you open a post to see the new comments, you reset this field to FALSE in the background.
Now you can query for posts where newcomments equalTo false
Or, instead of newcomments being a boolean, you could just as well use it for storing an array of pointers to the actual new comments (the afterSave hook updates this array with the pointer to the new comment). This way, you don’t need a second query for getting the new comments once you open the post. Here, you clear the newcomments property as soon as you’ve read the comments (or opened the post and have an array of the comments).
This storing of an array probably strikes a bad note in your SQL mindset, but this is one of many differences between SQL and NoSQL, since the latter is more focused on query efficiency than storage and consistency.
Or, if you don’t want to store this in the Post object, you could create a new PostTracker (or whatever) class to handle this. Maybe there are other things you would want to track (certainly, there might be in the future, even if you don’t have an idea for that at the moment).

Query on Parse relational data is not coming back ordered using orderedByAscending

I'm querying relation data on parse and I would like the objects to come back ordered by the date they were created. I've had this method work before but haven't been able to get an ordered query using relational data. The query return is in a random order. Thanks in advance! Here's my code:
PFQuery *postQuery = [PFQuery queryWithClassName:#"Post"];
[roomQuery whereKey:#"name" equalTo:self.postName];
NSError *error;
//done on main thread to have data for next query
NSArray *results = [postQuery findObjects:&error];
PFObject *post;
if ([results count]) {
post = [results objectAtIndex:0];
NSLog(#"results were found");
} else {
NSLog(#"results were not found");
}
PFRelation *commentsRelation = [#"Comments"];
[commentsRelation.query orderByAscending:#"createdAt"];
[commentsRelation.query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error Fetching Comments: %#", error);
} else {
NSArray *comments = objects;
}
I'm a little confused by your code,
you create a "postQuery", and call it, but never use any of its data.
There's also a roomQuery that never seems to have been allocated, or used.
You're querying a specific post by its name. Are you controlling its name? If not, you should use id's
what is PFRelation commentsRelation = [#"Comments"];
Probably because it's just a snippet, this stuff is dealt with elsewhere; however, for my answer, I'm assuming that your "comments" field is an array of "Comment" class objects.
Option 1:
PFQuery * postQuery = [PFQuery queryWithClassName:#"Post"];
[postQuery whereKey:#"name" equalTo:self.postName];
// again, possibly an id field would be more reliable
// [postQuery whereKey:#"objectId" equalTo:self.postId];
[postQuery includeKey:#"Comments"];
PFObject * post = [postQuery getFirstObject];// no need to download all if you just want object at [0]
// this will contain your post and all of it's comments with only one api call
// unfortunately, it's not sorted, so you would have to run a sort.
NSArray * comments = [post[#"Comments"] sortedArrayUsingComparator: ^(id obj1, id obj2) {
return [obj1[#"createdAt" compare: obj2[#"createdAt"];
}];
Option 2:
Perhaps a better option is to rework your data structure and instead of associating the comments to the post, you could associate the post to the comments (as in the parse docs)
PFQuery * postQuery = [PFQuery queryWithClassName:#"Post"];
[postQuery whereKey:#"name" equalTo:self.postName];
// again, possibly an id field would be more reliable
// [postQuery whereKey:#"objectId" equalTo:self.postId];
PFQuery * commentQuery = [PFQuery queryWithClassName:#"Comment"];
[commentsQuery whereKey:#"parent" matchesQuery:postQuery]; // when creating a comment, set your post as its parent
[commentsQuery addOrderDescending:#"createdAt"]
[commentQuery findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
// comments now contains the comments for myPost
}];
Both of the above solutions avoid making extra unnecessary api calls (parse charges based on calls after all!).

Resources