Parse - Using relations versus pointers? - ios

I am using Parse as my backend. I have problems setting up the correct relation between objects.
I basically have a class named Post, each post belongs to a user(PFUser), and when fetching a post I want the user info to be fetched along with the post.
#interface Post : PFObject<PFSubclassing>
#property (nonatomic, strong) NSDate *time;
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *body;
#property (nonatomic, strong, readonly) PFRelation *user;
// User in backed is defined as a relationship to _user
#end
// Saving the post
[post.user addObject:[PFUser currentUser];
[post saveInBackground];
This works fine and relates the post to that user, but when I try to fetch the post later, it doesn't seem like I can get an instance of _user from PFRelation.
What is the correct way to handle this?
Tried to change PFRelation to PFUser but that would crash because it tries to call save on the PFUser object

A Relation is for when you want a long list of related classes, where an array doesn't work, or when you want to query the related objects as needed and not have the list included every time you load the containing object.
Basically you have 4 options with Parse:
Pointer - single reference to another class (1 to 0..1)
Array - collection of pointers, loaded with the object every time (1 to 0..n, small lists)
Relation - collection of pointers, like a join table in SQL (handled under the covers for you), you must run a query against it to load values (1 to 0..n)
Custom join class - really just another object (like many-to-many join in SQL) with a Pointer to each side plus any related information (1..n to 1..n)
In your case a simple Pointer would do what you want.

In your usecase, a pointer is preferable over a PFRelation. You can include the user info by adding includeKey in your query:
[query includeKey:#"user"];
A way to get a comment count on your post is to add every new comment to an array of pointers in your Post.
It is easy to get stuck in the old SQLish ways when you start using NoSQL databases. If a counter is desirable, you could add a cloud code afterSave function on the comment object that updates the "comments" column of the Post class by adding a pointer to the saved comment to the "comments" array in Post. This way, when you fetch a post you can also use
[query includeKey:#"comments"];
which will give you the Post AND all the comments in one query. If you only need the count, you ommit the includeKey, but you still have an array in "comments" with the pointers, so the comment count is the length of the array.

You must create a query from the PFRelation object, like in this code snippet (taken from Parse documentation) and do an explicit query to retrieve the referenced object:
[[relation query] findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
// There was an error
} else {
// user(s)
}
}];
Parse provides the possibility to retrieve also referenced objects in the original query, and this is done using the "includeKey:" method in the original query (that is the query you setup to get the posts), by asking to return the user data and not just the reference. But I'm not sure if it works for PFRelation as in the documentation it is stated that includeKey works for PFObject and PFRelation is not a PFObject. You may try this code in any case and see if it works or not:
[query includeKey:#"user"]

Create A PFSubClass like
yourClassName:PFObject with PFSubclassing
and then in the header file create a Pointer relation
#property(nonatomic, strong) PFUser *userLikeRelation;
add in m file add
+ (NSString *)parseClassName {
return #"parseTableName";
}
+ (void)load {
[self registerSubclass];
}
finally in the View Controller set relation in query when you are save data in parse.
yourClassName *yourClassNameObj = [yourClassName objectWithClassName:[yourClassName parseClassName]];
[yourClassName setUserCommentRelation:[PFUser currentUser]];
for fetching data you can get data with include key
[yourClassNameObj includeKey:#"NameofRelation"];

Related

Relational database in Realm?

I want to create simple relational database in the iOS Realm DB. How can i link user id and there Wishlist product similar to the relational database in SQL. Example like bellow image :
I know i can create 2 separate Realm models (tables) to store user id with time_stamp and in second table for Wishlist where there are user id with each Wishlist products or user id and a Wishlist, where the user has an array of Wishlist. Now i want to store all users with there multiple wishlists. This means that every time the user enters in APP, I have to query every wishlists in existence to see whether its intended for this specific wishlists. The problem is in Wishlist table, it doesn't have any primary key and realm not allowing to create model without primary key.
Does Realm support foreign key concept ? and use of composite key is quit complicated. Is there a more efficient way to do this?
You can use RLMArray , where RLMArray is the container type in Realm used to define to-many relationships.
As mentioned in the official Realm documentation for objective c check example here.
Also go through this link it might help RLMArray doc
RLMArray is used for the relational database concept in realm. You can try like this:
#import <Realm/Realm.h>
#class User;
// User model
#interface User : RLMObject
#property NSString *name;
#property NSString *user_id;
#property RLMArray<Watchlist *><Watchlist> *watchlist;
#end
RLM_ARRAY_TYPE(Dog) // define RLMArray<Dog>
// Watchlist model
#interface Watchlist : RLMObject
#property NSString *id;
#property NSInteger *activity;
#property NSInteger *cost;
#end
RLM_ARRAY_TYPE(Watchlist) // define RLMArray<Person>
// Implementations
#implementation User
#end // none needed
#implementation Watchlist
#end // none needed
Read data from realm :
RLMResults *watchlistDB = [Watchlist allObjects];
WatchlistDB = [realm_data objectAtIndex:index];
RLMArray *realm_array = WatchlistDB.watchlist;

Fine Grained Notifications old/new RLMResults

I am trying to find out the inserted/deleted objects after a write transaction.
With the fine grained notification block I can get:
/// The indices of objects in the previous version of the collection which have
/// been removed from this one.
#property (nonatomic, readonly) NSArray<NSNumber *> *deletions;
/// The indices in the new version of the collection which were newly inserted.
#property (nonatomic, readonly) NSArray<NSNumber *> *insertions;
My question is in this snippet:
RLMResults *contacts = [CYRLMAddressBookContact allObjects];
RLMNotificationToken *token = [contacts addNotificationBlock:^(RLMResults *_Nullable results,
RLMCollectionChange *_Nullable change,
NSError *_Nullable error) {}];
Could contacts be considered a "previous version" of the collection?
If not is it safe to convert contacts to an NSArray and that would be a "previous version" of the collection?
In this code sample, when the notification block fires, contacts will already be in the updated state. The deletions and insertions values will be in relation to the updated state so that any UI elements still displaying the previous state of each item in contacts may be updated to match.
No, I don't think it's unsafe to do that. If you copied the contents of contacts to an NSArray, that would certainly capture the previous ordering of contacts before the notification. Realm objects are live though, so if the property values inside any of the contacts objects changed, that would be reflected in the array as well.
That being said, you do need to be careful when copying objects from RLMResults to an NSArray. Directly touching each object as you pass it to the array will cause it to be lazy-loaded by Realm, which may result in a performance hit.

Core Data Installation

I am using core data to save data from web services in my app. On the first time of running, app creates the core data instance and attributes and properties and save all the data. My question is that, when the application runs second time or many times, Is the core data creates its instance and properties and save all data again, or again and again? I am sorry if my question is not relevant.
Thanks
If the Webservice + code for storing data to coredata gets called every time you run app, core data will store objects again and again
You can solve this by 2 ways
If your data from server remains the same, you can set a flag and check that coredata insertion should execute only once
If you data from server may keep on changing, you can Update data instead of inserting it again to avoid duplicate records.
If u want to make sure the data is unique, u should make sure the data object should have a unique key.
For example:
#interface Person : NSManagedObject
#property (nonatomic, retain) NSNumber * pid;
#property (nonatomic, retain) NSString * name;
#end
The property of Person.pid should be unique. It means that u can only get one person max with the appointed pid.
So before u insert new object, u should query the db with NSFetchRequest(NSPredicate) like this:
NSNumber *aimPid;// the person's pid
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"pid == %#", aimPid]];
If person exist, then just update and save.
If person not exist, then insert a new one and save.
More:https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/

Parse iOS Subclassing. Check if array field contains value

I have two classes named Event and Tag (using LocalDatabase).
#interface Event : PFObject <PFSubclassing>
#property (nonatomic, strong) NSArray *tags;
#interface Tag : PFObject <PFSubclassing>
#property (nonatomic, strong) NSString *name;
tags field is an array of pointers to objects of Tag class.
I want to add a new tag to an event object and I need to check if there is no such tag in the array already. And if there is no such tag - add it.
What is the best approach to do it?
Additional question:
How can I query only objects from "tags" array (If, for example, I have several different array fields and lots of other fields and I don't want to use "includeKey")?
Edit:
As far as I understand, Array field in Parse contains only pointers to objects. So if I have, for example, object event with such fields as tags, people, places (which are arrays) then to get the actual data I will need to fetch event object using includeKey method. Or I can fetch the whole event object with all it's related data. That is what I DON'T want to do.
I want to have the array of, say, tags as a simple NSArray.
Something like
NSArray *tags = [event tags];
Thanks!
For your first question, you can not use containsObject from NSArray class because underlying it calls the isEqual for each elements. In Parse, there has a very convenient API to add unique object and you only need to call - (void)addUniqueObject:(id)object forKey:(NSString *)key.
For your additional question, I think they don't have something like that without querying using includeKey. After that, I think <Subclassing> category can enable that line of code you wrote.

Momento and 1 to Many Joins : CoreData Approach, Design Considerations and Opinions Sought

Given and coredata based app using an Indexcard metaphor. Each Indexcard can optionally have a one-to-many relationship with a number of other entities/tables; i.e. I'll use Momento's 'Moment' as a proxy for my Indexcard object and Momento's ancillaries of tags, locations, etc. , for these other objects/tables.
What is the 'fastest' way to show whether or not these foreign table relationships exist on probably the most important tableView in the entire app?
and
What would be the best approach for laying out the cell portion showing whether or not a relationship exists and the count of the number of each type of relationship?
Again, using Momento as a design pattern. With a link to a screenshot on Flickr (because stackOverflow won't let me post an image since I'm a noob.)
Maybe my ex-RDBMS stuff is contaminating my thinking, but they didn't do a mongo-join to get the values off to the right did they? [tags,events,people,locations]. There has to be a more elegant way that I'm just not seeing.
My thoughts for laying out the cells on the right was to possibly use some boolean if YES put up the icon and the count, but that seems pretty expensive for every cell.
I'm sure that the answer to this layout question would be driven by the approach taken in the first part of the problem. It doesn't seem that I would want to store ancillary relationships in the 'main/moment' IndexCard object for maintenance reasons.
Thanks in advance for any help.
If you set up a one-many relationship of indexcards to tags(or whatever), a fetched indexcard object should have an NSSet of tags as a property. Same for the others, and you should just be able to get the count of the set and display that next to each of the appropriate icons.
..Unless I'm misunderstanding your question.
edit: to answer the second part, you should indeed have a conditional in cellForRowAtIndex path that checks the count of each set and either just display it with the icon (possible to have 0 then, which is normally fine), or check whether it is 0 and hide the image if it is as you said. I don't think either solution will slow down your app since the data has already been fetched anyway by the time the cell is being rendered, but the solution where you just pass the count right through without checking if it's 0 would generally be fast overall.
edit to provide some sample code:
Your Core Data model would have an IndexCard entity and then an entity for each type of possible related object.
1)Model:
IndexCard - has a one-many relationship with each of the other entities
Tag
Location
Person
2)After creating this model and the corresponding Object classes you will end up with an IndexCard class that has the following in its header
#property (nonatomic, strong) NSSet *tags
#property (nonatomic, strong) NSSet *locations
#property (nonatomic, strong) NSSet *people
and of course the following in its implementation
#dynamic tags
#dynamic locations
#dynamic people
3)Now that we've established this Core Data model, we can perform an nsfetchrequest (of course when using a tableview, you should use an nsfetchedresultscontroller as it will dynamically fetch the IndexCards it needs as you're scrolling through the table). This code assumes that we have a usable NSManagedObjectContext in its scope (ideally passed in from the AppDelegate and set as an ivar) and that our IndexCard object has some sort of key/id property we can search by, lets call it "number"
NSNumber *numberWeWant = [NSNumber numberWithInt:1];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"IndexCard" inManagedObjectContext:ourContext];
request.predicate = [NSPredicate predicateWithFormat:#"number == %#", numberWeWant];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
//didn't bother error checking in case no IndexCard matches
IndexCard *ourCard = [results lastObject];
//you can now use these to display in the cell or hide the appropriate icons if they = 0
int numTags = ourCard.tags.count;
int numLocations = ourCard.locations.count;
int numPeople = ourCard.people.count;
//just a sample of how we would access the individual related objects
for(Tag *tag in ourCard.tags)
{
//do whatever you want with each tag here
}
Again, this code is just to fetch a single IndexCard. In an actual table you would be initializing an nsfetchedresultscontroller when loading the view that contains it, and then just accessing the IndexCard at the position matching IndexPath.row in cellForRowAtIndexPath.
This also assumes there are a finite number of types of objects that IndexCard can be related to. If the types can change and increase randomly, this approach would need to be modified.
Hopefully this helps.

Resources