JOIN TABLES parse.com - ios

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).

Related

parse web service relational database

I am using parse to develop an iOS app.
For the database, I have three tables, named user, UserAndCourse, and course.
Where UserAndCar stores a pointer to user and course.
the tables looks like:
My problem is:
How can I query the courses that belongs to the current user on iOS. i.e that look into UserAndCourse to find the rows that user column is current user and query for the course object that is pointed to by the course column.
Can I do this in a single relational query? or I have to query the UserAndCourse table rows first and then query the course it pointed to.
I suggest to use PFRelation, that way you don't need to create new table for relations like userAndCourse. Parse automatically will link through the relation. You just need to create relation between them and do what you want.
Saving relation;
PFUser *user = [PFUser currentUser];
PFRelation *relation = [user relationForKey:#"course"];
[relation addObject:coursePFObject];
[user saveInBackground];
And query when you need to reach them;
PFRelation *relation = [user relationForKey:#"course"];
PFQuery *query = [relation query];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
// results contains all the people who subscribe to course
}];
Moreover you can specify your course by adding more queries on it;
PFQuery *query = [relation query];
[query whereKey:#"name" equalTo:#"Math"];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
// results contains all the people who subscribe Math course
}];
I think it's better way to shape your tables in parse. Otherwise you need to create table for each relation.
Hope it helps also if you need more detail this can be helpful.

Handle one to many relation in Parse in 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

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: Multiple query results put into an Array

Scenario = I have an app where users can send each other Messages, Comments, and Pokes that are queried to populate the current user's notificationsTableView. There are 3 queries that must take place, one for Messages, two for Comments, and three for Pokes. The code I'm using is below...
PFQuery *messageQuery = [PFQuery queryWithClassName:#"Message"];
[messageQuery whereKey:#"receiverID" equalTo:[PFUser currentUser][#"userID"]];
[messageQuery orderByDescending:#"createdAt"];
[messageQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
messages = objects;
}];
PFQuery *pokeQuery = [PFQuery queryWithClassName:#"Poke"];
[pokeQuery whereKey:#"receiverID" equalTo:[PFUser currentUser][#"userID"]];
[pokeQuery orderByDescending:#"createdAt"];
[pokeQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
pokes = objects;
}];
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
[commentsQuery whereKey:#"receiverID" equalTo:[PFUser currentUser][#"userID"]];
[commentsQuery orderByDescending:#"createdAt"];
[commentsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
comments = objects;
}];
What is desired = To consolidate the following arrays: "messages", "pokes", and "comments" into a single array (notificationsArray) that I can sort by "createdAt" and populate my notificationsTableView with notificationsArray objectAtIndexPath:indexPath.row.
Problems I have encountered = (there are two)
(1) When I NSLog the results of any of these queries like so...
PFQuery *messageQuery = [PFQuery queryWithClassName:#"Message"];
[messageQuery whereKey:#"receiverID" equalTo:[PFUser currentUser][#"userID"]];
[messageQuery orderByDescending:#"createdAt"];
[messageQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
messages = objects;
}];
NSLog(#"messages = %#", messages);
It logs "messages = (null)". I can not for the life of me figure out why it is not being set. I know there are messages because when I NSLog the "objects" Array that comes from the query it gives me what I want. It's like the contents of the query will not leave the scope of the query itself. All of the queries above do this. If I can not get the contents of the query out of that block then I can not create an array of all of the arrays to populate the notificationsTableView with, so I'm screwed. Please help.
(2) Even if I do get the results from the queries into individual arrays, I am not sure how to create an array of arrays and order them by a key. Can anyone help me with this? Please.
You are probably looking for the +orQueryWithSubqueries:(NSArray *)queries method, but I don't understand what the return value description is:
a PFQuery that is the or of the passed in PFQueries
I'm thinking this means || (or) ?
You would do it like this:
NSArray *queryArray = [NSArray arrayWithObjects:messageQuery,pokeQuery,commentsQuery,nil];
PFQuery *allQueries = [PFQuery orQueryWithSubqueries:queryArray];
[allQueries findObjects... {
As for the second error, you are right, value is not retained because when the block loses scope all of the local variables inside get destroyed in the autoreleasepool. You need to retain this by using a strong property. self.messages = objects;
(1) You are logging messages outside of the callback function, and the log comes before the callback function returned. Try to log messages into your callback, just after assigning it.
[messageQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
messages = objects;
NSLog(#"messages = %#", messages)
}];
(2) Before sorting, create a NSMutableArray and use the addObjectsFromArray: method with each retrieved array.
To sort notifications, you should use a NSSortDescriptor, which is a mechanism that describes how to sort an array according to the format of contained objects. Here's an example that could match your needs:
NSSortDescriptor *createdAtDescriptor = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:YES];
notificationsArray = [messages sortedArrayUsingDescriptors:#[createdAtDescriptor]];
Hope this help!
EDIT: you can embed your temporary NSMutableArray into an autorelease pool to avoid useless memory leaks, so that the dedicated memory is freed just after you proceed to display.
EDIT: you can use orQueryWithSubqueries Parse method to merge several requests into a single one. It's not annoying in your case cause you're sorting PFObject according to their createdAt key, which is common to every PFObject. In any case, you will have to check PFObject types to display them according to their type. Please see full documentation here. Does not work for queries returning several kind of objects!

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