NSFetchedResultsController that contains objects from a relation - objective-c

I have a NSManagedObject with related objects. The relation is described by a keyPath.
Now I want to display these related objects in a table view. Of course I could just take the NSSet with these objects as a data source, but I'd prefer to refetch the objects with a NSFetchedResultsController to benefit from its features.
How can I create a predicate that describes these objects?

To display the related objects of a given object with a fetched results controller,
you would use the inverse relationship in the predicate. For example:
To display the children related to a given parent, use a fetched results controller
with the following fetch request:
Parent *theParent = ...;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Child"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"parent = %#", theParent];
[request setPredicate:predicate];
For nested relationships, just use the inverse relationships in inverted order. Example:
To display the streets of a given country:
Country *theCountry = ...;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Street"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"city.country = %#", theCountry];
[request setPredicate:predicate];

Thanks Martin, you gave me important information.
To generically get the key path I have found the following implementation:
// assume to have a valid key path and object
NSString *keyPath;
NSManagedObject *myObject;
NSArray *keys = [keyPath componentsSeparatedByString:#"."];
NSEntityDescription *entity = myObject.entity;
NSMutableArray *inverseKeys = [NSMutableArray arrayWithCapacity:keys.count];
// for the predicate we will need to know if we're dealing with a to-many-relation
BOOL isToMany = NO;
for (NSString *key in keys) {
NSRelationshipDescription *inverseRelation = [[[entity relationshipsByName] valueForKey:key] inverseRelationship];
// to-many on multiple hops is not supported.
if (isToMany) {
NSLog(#"ERROR: Cannot create a valid inverse relation for: %#. Hint: to-many on multiple hops is not supported.", keyPath);
return nil;
}
isToMany = inverseRelation.isToMany;
NSString *inverseKey = [inverseRelation name];
[inverseKeys insertObject:inverseKey atIndex:0];
}
NSString *inverseKeyPath = [inverseKeys componentsJoinedByString:#"."];
// now I can construct the predicate
if (isToMany) {
predicate = [NSPredicate predicateWithFormat:#"ANY %K = %#", inverseKeyPath, self.dataObject];
}
else {
predicate = [NSPredicate predicateWithFormat:#"%K = %#", inverseKeyPath, self.dataObject];
}
Update: I changed the predicate format so that it also supports many-to-many relations.
Update 2 This is getting more complex: I need to check my inverse relation if it is to-many and use different predicates. I updated the code example above.

NSPredicate *predicate = [NSPredicate predicateWithFormat:#"city.country = '%#'", theCountry];
You miss ' ' in predicateWithFormat string. Now it work.

Related

Core data delete single record from to many relation ship

I have created 4 entities.
User, Mobile, Email, Address.
User have to many relationship (name as numbers) with Mobile.
I have subclass for all of above. How to delete single mobile number from particular user? How to update single mobile number from particular user?,what Predicate use for that?
code is here:
NSManagedObjectContext *context = [self managedObjectContext]; NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:NSStringFromClass([User class])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY numbers.number == %#",self.textField.text];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error]; Mobile *objMobile;
User *objUser;
objUser = [array objectAtIndex:0];
for (User *obj in [objUser.numbers valueForKey:#"number"]) {
NSLog(#"%#",obj);
NSString *str1 = [NSString stringWithFormat:#"%#",obj];
NSString *str2 = [NSString stringWithFormat:#"%#",self.textDelete.text];
if ([str1 isEqualToString:str2] ) {
[objUser removeNumbersObject:objMobile]; }
}
NSLog(#"%#",[objUser.numbers valueForKey:#"number"]); self.textAddUserDetail.text = #"";
if (![context save:&error]) { }
Basically, don't search based on the user and the relationship. Instead, search directly for the number and then delete it or edit it:
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:NSStringFromClass([Number class])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"number == %#", self.textField.text];
Then you also don't need a loop and extra consideration of what to do.
It will be useful to use "Magical Records" Library.it work with Key value coding need not to maintain Relationships.it will Automatically get the unique Key From all the Entitys

Filtering NSArray with Predicate leaves extra result?

I have an NSArray containing NSDictionary objects.
I need to filter the objects based on a selection of values, so i have created an array of the values i want to filter by and using a predicatewithformat and feeding it the array of objects.
This is kind of working, but weirdly in situations where i know i should be getting an empty array returned i am getting a single object, that shouldn't be there.
I have logged out the value of the array of filter values, and i can clearly see that it contains a key which corresponds to the id_str of the object, so it shouldn't be returned.
Below is the code i am using, any pointers of where i am going wrong would be very vert helpful!
//Create new fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//Set new predicate to only fetch tweets that have been favourited
NSPredicate *filterFavourite = [NSPredicate predicateWithFormat:#"favouriteTweet == 'YES'"];
//Setup the Request
[request setEntity:[NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:_managedObjectContext]];
//Assign the predicate to the fetch request
[request setPredicate:filterFavourite];
NSError *error = nil;
//Create an array from the returned objects
NSArray *favouriteTweets = [_managedObjectContext executeFetchRequest:request error:&error];
NSAssert2(favouriteTweets != nil && error == nil, #"Error fetching events: %#\n%#", [error localizedDescription], [error userInfo]);
//Create a new array containing just the tweet ID's we are looking for
NSArray *favouriteTweetsID = [favouriteTweets valueForKey:#"tweetID"];
NSLog(#"%#", favouriteTweetsID);
//Create a new predicate which will take our array of tweet ID's
NSPredicate *filterFavouritsearchPredicate = [NSPredicate predicateWithFormat:#"(id_str != %#)" argumentArray:favouriteTweetsID];
//Filter our array of tweet dictionary objects using the array of tweet id's we created
NSArray *filteredTweets = [self.timelineStatuses filteredArrayUsingPredicate:filterFavouritsearchPredicate];
//Send those tweets out to be processed and added to core data
[self processNewTweets:filteredTweets];
NSLog(#"Update Favoutited Tweets: %#", filteredTweets);
This is probably not doing what you intent:
[NSPredicate predicateWithFormat:#"(id_str != %#)" argumentArray:favouriteTweetsID];
It is equivalent to
[NSPredicate predicateWithFormat:#"(id_str != %#)", id1, id2, ... idN];
where id1, id2, ..., idN are the elements of favouriteTweetsID.
The format string has only one format specifier,
so that everything but
the first element is ignored and you just have
[NSPredicate predicateWithFormat:#"(id_str != %#)", id1];
If you want to filter all objects where id_str is not equal to any of the array elements,
use
[NSPredicate predicateWithFormat:#"NOT id_str IN %#", favouriteTweetsID];

Core Data - Saving an Entity as a pList / dictionary

I have a very simple app that has one CoreData Entity with a circular relationship to itself. I want to use this data as a plist in another app.
The relationship is 'to one' on the parent side & 'to many' on the child side.
Basically a tree with one item at the top and then children of that object, children of the child objects, etc...
I want to create a dictionary/pList from this data with the structure of the entity including the relationships. (Does that make sense?)
So far, with the help of other answers here, I can get a plist of all the objects (but all on the same "level") or a plist of the ultimate parent object (but the children relationship is faulted).
This is what I'm doing to try and get the data and save it to my desktop:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:ENTITY_NAME];
request.resultType = NSDictionaryResultType;
NSError *fetchError = nil;
NSArray *results = [self.databaseDocument.managedObjectContext executeFetchRequest:request error:&fetchError];
NSLog(#"%#", results);
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [caches stringByAppendingPathComponent:#"/../../../../../../../../Desktop/my.plist"];
[results writeToFile:path atomically:NO];
At the moment there are only three objects. A main object, a child of that object, and a child of that child object.
This is giving me an plist which is an array with three objects in it.
If I include a predicate to only get the object that has no parent like this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:ENTITY_NAME];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"parent == nil"];
request.predicate = predicate;
NSError *fetchError = nil;
NSArray *results = [self.databaseDocument.managedObjectContext executeFetchRequest:request error:&fetchError];
NSLog(#"%#", results);
Item *mainItem = [results lastObject];
NSArray *keys = [[[mainItem entity] attributesByName] allKeys];
NSDictionary *dict = [mainItem dictionaryWithValuesForKeys:keys];
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [caches stringByAppendingPathComponent:#"/../../../../../../../../Desktop/my.plist"];
[dict writeToFile:path atomically:NO];
This gives me a directory but does not include the children.
Can anyone explain to me how I can get the plist structured like my entity is? In other words just one Directory with all the children contained within, and the children of each child within the Directory for that child, and so on...?
There may well be a better way to export your Core Data entities to a plist while preserving the hierarchical structure of the relationships - I'm willing to be educated. I would approach processing them in a recursive method like this: (this is completely untested - meant only for illustrative purposes - also I'm not completely sure how you want the plist structured, etc.)
- (void)processEntity:(NSManagedObject *)object forMutableArray:(NSMutableArray *)mutableArray {
NSMutableDictionary *entityInfo = [NSMutableDictionary new];
// populate entityInfo with your object's properties
// however you are doing that now...
// if this object has children - process them
NSMutableArray *childArray = [NSMutableArray new];
for( id child in object.children ) {
[self processEntity:child forMutableArray:childArray];
}
[entityInfo setObject:childArray forKey:#"children"];
[mutableArray addObject:entityInfo];
}
So, you start the process by iterating over the top-level objects (those with no parent) then process them in processEntity:forMutableArray:

Is this approach to insert/edit of Core Data nested objects the most efficient?

I have the following Core Data model (simplified for example):
Person
->Address
City
>Region
RegionName
>Country
CountryName
When a new Person is created
NSManagedObjectModel *objectModel=[[AppCoreData sharedInstance]objectModel];
NSEntityDescription *entity=[[objectModel entitiesByName] valueForKey:#"Beverage"];
Person *person=(Person*)[[NSManagedObject alloc]initWithEntity:entity insertIntoManagedObjectContext:nil];
a Person entity is created w/o inserting into the object context so it is easy to abandon the insert if necessary.
Then the user can select a Region which may or may not exist in the database. A search is performed to see if the Region exists
NSEntityDescription *entityDescription=[NSEntityDescription entityForName:#"Region" inManagedObjectContext:self.objectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
request.returnsObjectsAsFaults=NO;
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"id == %#",region.id];
[request setPredicate:predicate];
NSError *error=nil;
NSArray *array= [self.objectContext executeFetchRequest:request error:&error];
if ([array count] == 1) {
//use existing object
person.region=(Region*)[array objectAtIndex:0];
} else {
//create new object
NSEntityDescription *entity=[[objectModel entitiesByName] valueForKey:#"Region"];
self.collectionItem.beverage.region=(Region*)[[NSManagedObject alloc]initWithEntity:entity insertIntoManagedObjectContext:nil];
person.region.id=[NSNumber numberWithInt:[regionID intValue]];
person.region.regionName=regionName;
}
Finally, if the user does not abandon the insert, the person object is inserted into the object context and saved
[self.objectContext insertObject:self.collectionItem];
What is the best approach to dealing with nested Core Data objects where the nested objects, e.g., Region may or may not exist in Core Data? Examples or references appreciated.

Core Data & Generating Model Entities

Standard newbie question. I've created a data model for an iOS application. I am able to create, update and delete entities within the model from various views by using the NSEntityDescription object.
Say if I had a mutable array of objects returned from a fetch request. How can I loop through each one when I do not have a generated object definition from the entity model? By generated object definition I mean, a header and body class definition of the entity described in the data model package.
All CoreData entities derive from NSManagedObject and all the database data from those can be accessed via key value encoding. The minimum you need to know can be gained from the Model. You don't necessarily require the headers.
For example an entity PersonEntity which has a relationship to NameEntity with attribute firstname
NSArray *results = [managedObjectContext queryEntityForName:#"PersonEntity" predicateFormat:nil argumentArray:nil];
for(NSManagedObject *object in results)
{
NSString *name = [object valueForKeyPath:#"nameobject.firstname";
[self doSomething:name];
}
queryEntityForName is my own category. You might find it useful.
#implementation NSManagedObjectContext(VMQueryAdditions)
-(NSArray *)queryEntityForName:(NSString *)name predicateFormat:(NSString *)pstring argumentArray:(NSArray *)arr
{
NSEntityDescription *entity = [NSEntityDescription entityForName:name inManagedObjectContext:self];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:entity];
NSPredicate *pred;
if(pstring)
{
if(arr) pred = [NSPredicate predicateWithFormat:pstring argumentArray:arr];
else pred = [NSPredicate predicateWithFormat:pstring];
[fetch setPredicate:pred];
}
NSError *error = nil;
NSArray *results = [self executeFetchRequest:fetch error:&error];
if (error) {
NSLog(#"MOC Fetch - Unresolved error %#, %#", error, [error userInfo]);
return [NSArray array];
}
return results;
}
#end