Core Data: Using Predicates with to-many Relationship - core-data

To-Many relationship in Entity "Meetings" <--->> "Users"
I made a NSManagedSubclass for "Meetings" and it has a property:
#property (nonatomic, retain) NSSet *users;
Objects in *users save fine and I can see them. But when I try to fetch, nothing happens. I have the breakpoint setup inside the Block and it seems like the fetch Block never reaches the breakpoint. No errors. Applications is still running.
I have the following code:
NSString *userName = #"iphone";
NSLog(#"Username %#", userName);
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Meeting"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"users.username == %#", userName];
NSLog(#"Predicate %#", predicate);
[request setPredicate:predicate];
[self.managedObjectContext executeFetchRequest:request onSuccess:^(NSArray *results)
{ // I have setup the breakpoint here
NSLog(#"results.count %i", results.count);
if(results.count > 0)
{
NSLog(#"object found");
}
}onFailure:^(NSError *error) {
NSLog(#"There was an error! %#", error);
}];
Log:
2013-10-12 20:10:19.200 App[3128:c07] Username iphone
2013-10-12 20:10:19.200 App[3128:c07] Predicate users.username == "iphone"
I can fetch for other attributes in Meetings just fine. i.e. If I replace the predicate line with :
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"meetings_id == %#", meetings_id];
And the breakpoint will stop the code, the will show "object found".
Again, I can see the "iphone" user inside the users relationship. What am I doing wrong?

For to-many relationships you have to use a predicate modifier, that states what is the intended behaviour. Since you are searching for Meetings, CoreData cannot figure out from your predicate users.username == %#, if you want Meetings where ALL users have the specified username or only those where ANY single user meets the requirements (see here for in-depth documentation).
Try this for a change, since I think it is what you would want:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY users.username == %#", userName]

Related

Saving Core Data related data and retrieving with NSPredicate and NSFetchedResultsController with multiple entities

I'm fairly new to Core Data and am still trying to understand accessing and filtering related data. My problem is either I'm not getting the data correctly into the managedObjectContext or I'm not pulling it out correctly. (I think the first, but I'm not sure.)
Here's my data model with two entities related one to many: (I plan to refactor once I get one level of relationship working.)
I have a SeasonsVC in which you click on a season name and the list of games for that season is supposed to appear in the GamesVC and you have the option to add or edit an existing game. This works fine at a first pass. I can add and edit games via this code in the GameDetailsVC:
-(IBAction)done:(UIBarButtonItem *)sender {
Game *game = nil;
if (self.gameToEdit != nil) {
game = self.gameToEdit;
NSLog(#"Hitting gametoedit");
} else {
game = [NSEntityDescription insertNewObjectForEntityForName:#"Game" inManagedObjectContext:self.managedObjectContext];
NSLog(#"Hitting new game");
}
game.opponent = self.opponentTextField.text;
//season.seasonDescription = self.seasonDescriptionTextView.text;
NSLog(#"Game to edit: %#", game.opponent);
//NSLog(#"Season: %#", season);
//NSLog(#"MOC in done: %#", self.managedObjectContext);
//NSLog(#"Season name: %#", season.seasonName);
[self dismissViewControllerAnimated:YES completion:nil];
}
I can then see the games in the GamesVC via the fetchedResultsController and delegate methods, but each game is associated with every season. Once I try to filter the data with a predicate so that I only see the games that were added for that season all the games disappear. Here's the code for that from the GamesVC:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Game" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"Season name for predicate %#", self.season.seasonName);//shows correct season name
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"season.seasonName like '%#'", self.season.seasonName]];
[fetchRequest setPredicate: predicate];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"dateOfGame" ascending:NO] ;
[fetchRequest setSortDescriptors:#[sortDescriptor]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root2"];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Since I can log the correct season name right before the predicate statement, I think that the added games are not getting "associated" with the correct season when I put them in the MOC in the done: method shown above; otherwise, I'd expect the predicate to find them.
Can you help this rookie? Thanks.
A day later I have this sorted. I needed to make three changes to what I was doing.
First, in my prepareForSegue in my GameListVC I needed to pass the season along to the GameDetailsVC by changing the code to this. (Just added the one line: controller.season = self.season)
//pass the Season object to be edited
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Game *gameToEdit = [self.fetchedResultsController objectAtIndexPath:indexPath];
controller.gameToEdit = gameToEdit;
controller.season = self.season;
Second, I had to put the new/edited game object into the MOC by adding these two lines to my done: method in the GameDetailsVC.
NSMutableSet *games = [self.season mutableSetValueForKey:#"games"];
[games addObject:game];
Third, I needed to simplify my predicate statement in my fetchedResultsController method in the GameListVC.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"season.seasonName like %#", self.season.seasonName];
So, to answer my own question, I was both not saving the game linked to the season and my predicate was wrong.
I hope some of this helps another rookie.

Fetch one-to-many relationship fault still shows after adding setRelationshipKeyPathsForPrefetching

Let's say my data model has two entities: Department and Person. Departments has a to-many relationship called 'departmentToPerson' with Person. Person has a to-one relationship called 'personToDepartment' with Department.
I want to populate an array of people belonging to a selected department. To selected the department I've created a UILabel that displays a departmentName that is selected from a popup tableview. When I run the app the log shows:
personToDepartment.departmentName == (entity:
Department; id: 0x8cc1920
;
data: {
departmentName = Marketing;
departmentToPerson = "";
I've read the purpose of faults and implemented setRelationshipKeyPathsForPrefetching, but still I get the fault. I'll put in the disclaimer that I'm a newbie and may be missing something obvious. When I delete the predicate my table view populates with all personName. Here's the code:
- (NSFetchedResultsController *)fetchedResultsController {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Person" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"personToDepartment.departmentName = %#", selectedDepartment];
NSLog(#"predicate is: %#",predicate);
[fetchRequest setPredicate:predicate];
[fetchRequest setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObjects: #"departmentToPerson", nil]];
fetchRequest.returnsObjectsAsFaults = NO;
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"personName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Again, I select a new department from the UILabel, the log displays selectedDepartment, but then states relationship fault and does not populate the table.
Thanks in advance for the help!
Updated 9JUNE
I've also found this works:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"personToDepartment = %#", selectedDepartment];
I'm also kind of newbie to CoreData, but it seems to me that predicate is wrong. According to Log record you're about to comparing personToDepartment.departmentName to a department Object. Predicate should look like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"personToDepartment.departmentName = %#", selectedDepartment.departmentName];
But there is a better way. selectedDepartment.departmentToPerson will return an NSSet with all persons belonging to this department (if relationship was set previously). But warning, if you'll try to NSLog(#"%#", selectedDepartment.departmentToPerson) probably you'll get "relationship fault", because CoreData will not do fetch until you address specific object in NSSet or enumerate through it. But for example NSLog(#"%d", selectedDepartment.departmentToPerson.count) will force CoreData to make fetch and you'll see number of persons in department.
UPDATE:
NSSet is empty probably because you are not setting relationship, when creating person's object. Inverse To-Many relationship from department to persons will be set automatically.
- (id)insertNewPerson:(NSDictionary *)personInfo inDepartment:(Department*)departmentObject
{
Person *personObject = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
personObject.name = personInfo.name;
personObject.departmentName = departmentObject.name;
...
personObject.personToDepartment = departmentObject; //this is RELATIONSHIP, if set, then you can access all persons from specific department by doing: departmentObject.departmentToPerson
}

Getting stored values from NSSet which was fretched from core data as to-many

I am actually new to iPhone programming and have successfully create an app but I can't figure out how to retrieve the values stored in NSSet. I have 2 entities in core data related to each other. Users one-to-many to Scores. Users entity has firsNname and lastName attributes and Scores entity has onTime, wasAbsent, and dateIn attributes. I fetch using predicate based on firstName and lastName and then execute the fetch. The fetch is successful and I am able to get both entities in one fetch call. However I cannot get values for the values from Scores entity. Whatever I do, it always returns NSSet object. What I want to do is to retrieve the boolean value which was stored in onTime and wasAbsent attributes and feed them to UISwitch.
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
self.managedObject = delegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Student"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"firstname == %# AND lastname == %#", #"James", #"Smith"];
NSError *error = nil;
self.scoresArray = [self.managedObject executeFetchRequest:fetchRequest error:&error];
if ([self.scoresArray count]) {
Student *student = [self.scoresArray objectAtIndex:0];
NSSet *score = student.scores;
NSArray *arr = [score allObjects];
NSLog(#"%#", [arr objectAtIndex:0]);
}
if I can directly access the Score entity instead of using NSSet, that would be ideal that way I can reference it using a dot notation.
Any help would be appreciated. Thanks
Use something like the following:
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = delegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Student"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"firstname == %# AND lastname == %#", #"James", #"Smith"];
NSError *error = nil;
NSArray *studentArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (Student *student in studentArray) {
Student *student = [self.studentArray objectAtIndex:0];
NSLog(#" student is %# %#", student.firstname, student.lastname);
for (Score *score in student.scores) {
// Do something with score
NSLog(#" score is %#", score);
}
}
Because Student has to-many relationship to Scores (and the property for it is scores), when you get the value of student.scores, it returns an unordered collection, which is NSSet in Foundation. So you just work with it like you work with NSSet and with collections in general.

Assigning fetched NSManagedObject to a property

This question may seem long, but I'm sure it's relativity simple for Core-Data experts. Showing the configuration made this Question long. Thanks!
In CoreData I have a User entity and an NSManagedObject subclass (User.h/.m) created from that entity. Not sure if it's relevant, but I'm using remote Database with Stackmob.
Here is what my fetch request looks like in Review.m:
Fetch Request:
-(NSArray *)fetchRequest
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Please Note the line below:
[fetchRequest setReturnsObjectsAsFaults:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username == %#", self.usernameField.text];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
return fetchedObjects;
}
if (fetchedObjects.count>=1)
{
NSLog(#"Number of Objects Returned %i", fetchedObjects.count);
NSManagedObject *fetchedObject = [fetchedObjects objectAtIndex:0];
}
Here is the log:
2013-08-18 10:26:25.082 Time[995:c07] Number of Objects Returned 1
2013-08-18 10:26:25.082 Time[995:c07] User Object: <User: 0xe07b260> (entity: User; id: 0xa65ab70 <x-coredata://392983AD-D649-4D68-A93F-D86109BC009C-995-000004B584F1BB06/User/piphone5> ; data: <fault>)
Several weeks ago I had created the User Object successfully with the following:
User *newUser = [[User alloc] initIntoManagedObjectContext:self.managedObjectContext];
[newUser setValue:self.usernameField.text forKey:[newUser primaryKeyField]];
[newUser setValue:self.emailAddressField.text forKey:#"email"];
[newUser setPassword:self.passwordField.text];
[newUser setValue:self.gender forKey:#"gender"];
[self.managedObjectContext saveOnSuccess:^{
[self.client loginWithUsername:self.usernameField.text password:self.passwordField.text onSuccess:^(NSDictionary *results) {
NSLog(#"Login Success %#",results);
} onFailure:^(NSError *error) {
NSLog(#"Login Fail: %#",error);
}];
This is what I would like to do in Review.m:
#interface Review()
#property (strong, nonatomic) User *user;
#end
#sythesize = user;
....
if (fetchedObjects.count>=1)
{
NSLog(#"Number of Objects Returned %i", fetchedObjects.count);
NSManagedObject *fetchedObject = [fetchedObjects objectAtIndex:0];
NSLog(#"fetchObject Object %#", fetchedObject);
//Addition
user = (User*)(fetchedObject);
NSLog(#"User %#", user);
}
Here is the log:
2013-08-18 11:07:13.313 Time[1177:c07] Number of Objects Returned 1
2013-08-18 11:07:13.314 Time[1177:c07] fetchObject Object <User: 0xa532cc0> (entity: User; id: 0xb426de0 <x-coredata://3336122B-7117-4D92-B0A1-DDBAF80DDBF7-1177-000006EEE046E326/User/piphone5> ; data: <fault>)
2013-08-18 11:07:13.314 Time[1177:c07] User <User: 0xa532cc0> (entity: User; id: 0xb426de0 <x-coredata://3336122B-7117-4D92-B0A1-DDBAF80DDBF7-1177-000006EEE046E326/User/piphone5> ; data: <fault>)
Why is this not working? User still shows up as Fault? I have the following line in fetch Request: [fetchRequest setReturnsObjectsAsFaults:NO];
The Following is the reason I need to do this:
Notification *notificationObject = [NSEntityDescription insertNewObjectForEntityForName:#"Notification" inManagedObjectContext:self.managedObjectContext];
[notificationObject setValue:[NSNumber numberWithInt:1] forKey:#"appType"];
[notificationObject setValue:[notificationObject assignObjectId] forKey:[notificationObject primaryKeyField]];
NSError *error = nil;
if (![self.managedObjectContext saveAndWait:&error])
{
NSLog(#"There was an error");
}
else
{
NSLog(#"Notification Object Created");
}
//I need to Add the fetchedObject as UsersObject (which is a relationship)
[notificationObject addUsersObject:user];
[self.managedObjectContext saveOnSuccess:^{ ....
Here is my CoreData Setup:
Why is this not working? User still shows up as Fault? I have the following line in fetch Request: [fetchRequest setReturnsObjectsAsFaults:NO];
This is a StackMob restriction. In https://developer.stackmob.com/ios-sdk/core-data-guide
it is explicitly stated that returnObjectsAsFaults / setReturnObjectsAsFaults:
is not (yet) supported.
A managed object showing up in the log as fault is fine. Use it or access its properties, and Core Data will fill the faults in the most efficient way with a minimum strain on resources.
This last part is important because it is the reason you don't really want to change the faulting behavior of Core Data. The necessity to send a notification does not change that.
Also, referring to the faulting section in the Core Data Programming Guide:
Batch faulting
You can batch fault a collection of objects by executing a fetch request using a predicate with an IN operator, as illustrated by the following example. [...]
NSArray *array = [NSArray arrayWithObjects:fault1, fault2, ..., nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self IN %#", array];
In OS X v10.5 and later, when you create a fetch request you can use the NSFetchRequest method setReturnsObjectsAsFaults: to ensure that managed objects are not returned as faults.
So, you see, the special fetch request shortcut is only available to OS X. Read on at above source to check out the alternative, pre-fetching.

How do I check if an NSSet contains an object of a kind of class?

How would you implement the following instance method for NSSet:
- (BOOL)containsMemberOfClass:(Class)aClass
Here's why I want to know:
Core Data model:
How do I add a Facebook authorization to a user's authorizations NSSet, but only if one doesn't already exist. In other words, a user can have many authorizations (in case I choose to add a Twitter (e.g.) authorization in the future) but should only have one of each kind of authorization. So, if (![myUser.authorizations containsMemberOfClass:[Facebook class]]), then add a Facebook authorization instance to myUser.
Unfortunately, you have to loop through all of the authorizations and check:
#interface NSSet (ContainsAdditions)
- (BOOL)containsKindOfClass:(Class)class;
- (BOOL)containsMemberOfClass:(Class)class;
#end
#implementation NSSet (ContainsAdditions)
- (BOOL)containsKindOfClass:(Class)class {
for (id element in self) {
if ([element isKindOfClass:class])
return YES;
}
return NO;
}
- (BOOL)containsMemberOfClass:(Class)class {
for (id element in self) {
if ([element isMemberOfClass:class])
return YES;
}
return NO;
}
#end
With Core Data it is actually best practice to use an NSFetchRequest with an NSPredicate. To do that you would have to add an attribute to your Authorization object, something like authorizationType. You could then do the following:
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY user.authorizations.authorizationType == %#", #"facebook"];
[request setEntity:[NSEntityDescription entityForName:#"Authorization" inManagedObjectContext:moc]];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *result = [moc executeFetchRequest:request error:&error];
You can then check the count of result to see if it exists or not. Using NSPredicate allows you to use any optimizations Apple has added around CoreData. Here are the Apple Docs.
What if you add this instance method to the User class?
- (BOOL)hasFacebookAuthorization {
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"user.id == %#", [self id]];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Facebook"
inManagedObjectContext:managedObjectContext]];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *result = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (!result) {
// TODO: Handle the error appropriately.
NSLog(#"hasFacebookAuthorization error %#, %#", error, [error userInfo]);
}
return [result count] > 0;
}
Your going about this the wrong way. If the types of authorization are something common and important then you should model that in your data model directly instead of trying to impose that structure in external controller code.
If you have a fixed set of authorizations then you should create an entity to model those authorizations.
Facebook{
accessToken:string
experationDate:date
authorization<-->Authorizations.facebook
}
Google{
username:string
password:string
authorization<-->Authorizations.google
}
Authorizations{
user<-->User.authorizations
facebook<-->Facebook.authorization
google<-->Google.authorization
}
Now you have all your authorizations captured in the data model where they belong. To tighten things up more. You could add a custom method to the User class to control adding and removing authorizations.
The key idea here is that all the logic that manages data should be encapsulated in the data model to the greatest extent possible. This allows you to (1) test all data operations independent of the interface and (2) easily add and remove interface elements without breaking the data model.
Old question, but slightly different answer:
NSPredicate *objectTypeFilter = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject isKindOfClass:NSClassFromString(#"MyObject")];
}];
NSSet *myObjects =[allObjects filteredSetUsingPredicate:objectTypeFilter];
if (myObjects.count>0) {
// There was at least one object of class MyObject in the allObjects set
}

Resources