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.
Related
Imagine that we have an application that displays articles. Let's say that every article has such fields:
id
title
author
text
So we create object to store articles:
#interface Article : NSObject
#property int article_id;
#property NSString *title;
#property NSString *author;
#property NSString *text;
If we need to display one full article - we ask the server for information about the article by it's id. Server responces with JSON (for example), and we have all fields - id,caption,title,text on that JSON. We can deserialize it and our object will have all fields.
But what if we need to get a list of articles from the server (display them it TableView for example)? We don't need all data for this, so server sends us JSON array with only id and caption for every article. If we deserialize that dictionaries, our object will have nil fields (author and text). Is this normal to use that object? Or we need to create some special class without that fields, and use it for news list?
Something like this:
#interface ArticleForTableView : NSObject
#property int article_id;
#property NSString *title;
What is the correct way to send selected article object to detail controller? We need to create new Article object with all fields filled (if it it correct to use ArticleForTableView), or fill fields of exiting object (if it is correct to use Article with nil fields), or something else?
Someone else may disagree with me on this, but I don't think you really need to make another object class just to handle the different use case. As long as you are properly handling potential null values for the fields of your class then its really just up to you to make sure you have the data you need when you actually need to display it.
So if you only have id and caption in the table view, when you select a table view cell you could push your detail controller, pass the Article object you selected into detail controller and then retrieve the remaining information on demand for that article if it has not already been pulled from the server.
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"];
I am implementing this SO post Custom Core Data SectionNameKeyPath : in particular, Martin R's first suggestion of adding a category method to feed my sectionNameKeyPath.
There, they have one persistent attribute called acctPeriod to be used for the sort descriptor, and one category method called periodYear which works on acctPeriod (to be precise, extracts its first 4 characters). However, I have many persistent attributes like acctPeriod, any of which could be chosen by the user for the sort descriptor, and I want to implement the same method on them to feed as sectionNameKeyPath. In other words, I want to pass acctPeriod as an argument to the category method, like periodYear:(NSString*) acctPeriod
Is this possible? How would the code for such a category method and FRC (sectionNameKeyPath) look?
Thanks!
The "section name key path" is a key path that is applied to each managed object
and returns the section name, i.e. the
fetched results controller calls
[object valueForKeyPath:<sectionNameKeyPath>] for each object when dividing the table
into sections.
If the key path is implemented as a (category) method, it has to be
a method without arguments, using only the implicit argument self.
Therefore I do not see how an additional parameter can be
used in that method (unless you work with some global variables).
But if the method to create the section name is exactly identical for all
possible attributes (like taking the first four characters of a string in the linked example), you can implement that method as a category on NSString:
#interface NSString (MyAdditions)
- (NSString *)firstFourCharacters;
#end
#implementation NSString (MyAdditions)
- (NSString *) firstFourCharacters {
if ([self length] <= 4)
return self;
return [self substringToIndex:4];
}
#end
Then you dynamically create a section name key path of the form <yourAttribute>.firstFourCharacters with
sectionNameKeyPath:[NSString stringWithFormat:#"%#.firstFourCharacters", yourAttribute]
in the fetched results controller, where yourAttribute is the persistent attribute that
is currently used for sorting the table view.
The Key-Value Coding machinery will
(for each managed object) first apply <yourAttribute> to the object, and then apply firstFourCharacters to the result.
As was pointed out, this should be trivial:
#interface Transaction (AdditionalMethods)
-(NSString*)sectionStringForPeriod:(NSNumber*)acctPeriod;
#end
Setup - using RestKit, along with it's abilities to store data in a CoreData store.
I'm trying to perform two separate GET operations:
issue/:issueId ==> this returns an Issue object, assuming one with that ID exists.
issue/:issueId/comment ==> this returns Comment objects, belonging to the issue matching issueId.
So, for the first call, that just gets an issue back. It will only return comments back if I pass in an extra parameter on the URL. Otherwise, it won't. Of course, if I do ask for it, then the objects get created just fine, and all the objects are connected correctly in my core-data store.
The objects that I'm mapping look like this:
#interface Issue : NSManagedObject
#property (nonatomic) int32_t issueId;
#property (nonatomic, retain) NSSet* comments;
// many other fields not shown.
#end
#interface Comment: NSManagedObject
#property (nonatomic) int32_t commentId;
// many other fields not shown.
#end
Issue has a collection of Comments. Comments don't know about their owning Issue.
So, all I'm trying to do is make it possible for both of these calls to exist.
For example, in our URLs, say "issueId" is 12345. So, if I make one call to http://example.com/issue/12345, I'd like the data to be written to my CoreData store. (This works great, btw). What I would like to happen next is to call "http://example.com/issue/12345/comments", and then have those comments write to the CoreData store, and also be connected to issue-12345, that's already there. That's the part that I'm having trouble with.
If anyone could offer guidance on this, I'd really appreciate it.
After reading this issue on the official repo, I would proceed like follows.
In you Core Data model add the inverse relationship Comment -> Issue, so that your Comment interface looks like
#interface Comment: NSManagedObject
#property (nonatomic, retain) Issue * issue;
#property (nonatomic) int32_t commentId;
// many other fields not shown.
#end
and make that relationship mandatory.
Now you have to setup your mapping adding that relationship, for instance
[issueMapping addRelationshipMappingWithSourceKeyPath:#"comments"
mapping:[self commentMapping]];
If my understanding is correct, RestKit should populate both relationships (the one-to-many Issue -> Comment and its inverse) for you.
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.