CoreData: NSPredicate based on relationships - core-data

I'm working on a recipe book right now using Core Data. It's the first time I'm using CoreData and it's working so far, but I'm having some trouble using it in the iPad's split view.
Here's my object model:
http://i621.photobucket.com/albums/tt295/Naosu_FFXI/ObjectModel.png
In my app, steps and ingredients are shown in two tables in the detail view. When I have one recipe, it works as expected. However, the NSFetchedResultsControllers for both tables pulls all the information regardless of what recipe you select. So using an NSPredicate seems to be the most obvious choice.
Here is my code snippet for the NSPredicate:
filteredIngredientsArray = [[NSMutableArray alloc] init];
.... snip ....
//---------- Filtering the ingredients by name ----------
NSError *error = nil;
NSPredicate *ingredientsPredicate = [NSPredicate predicateWithFormat:#"recipe.recipeName == '%#'", selectedName];
[fetchRequest setPredicate:ingredientsPredicate];
NSLog(#"Filtering the INGREDIENTS with this: %#", selectedName);
NSArray *loadedIngredients = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
filteredIngredientsArray = [[NSMutableArray alloc] initWithArray:loadedIngredients];
[self.ingredientTable reloadData];
When I use this, my tables don't get filled period.... so it's definitively working, but not working as I want it to. My NSLog shows that the value of the selectedName variable is the name of the recipe the user taps on.
This has been driving me up the wall and it really bothers me, so any feedback/help would be appreciated.

Remove the single quotes around %# in the predicate format:
[NSPredicate predicateWithFormat:#"recipe.recipeName == %#", selectedName]

Related

Many To Many NSPredicate With NSFetchedResultsController

I am having trouble creating an NSPredicate object based on my data model configuration.
I have 2 entity's one called Bob and one called Fred. Bob has a many to many relationship called 'hasFreds' (the reverse is hasBobs) with Fred and Fred has a property hasEaten of type BOOL.
I want to fetch a list of Bob's where every Bob has at least one Fred with hasEaten=YES. If there are no Freds with hasEaten=YES, I want to return all Bobs with no Freds.
This is what I have so far and it doesn't quite work (it doesn't meet the "If there are no Freds with hasEaten=YES, I want to return all Bobs with no Fred" condition):
predicate1 = [NSPredicate predicateWithFormat:
#"(SUBQUERY(hasFreds, $fred, $fred.hasEaten = %#).#count > 0)"
,#YES];
fr = [NSFetchRequest fetchRequestWithEntityName:#"Bob"];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:NO selector:#selector(compare:)]];
fr.predicate = predicate1;
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fr managedObjectContext:[[self appDelegate] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
fetchedResultsController.delegate = self;
I think that from a concept point of view your conditions are not really one query. You want to make two queries, one based on the outcome of the other one. This is not really what SQL queries, let alone object graph predicates, were conceived for.
Thus, I believe the simplest and most intuitive solution should be to have two versions of your fetched results controller.
You create the other version by changing the predicate dynamically. (I am using this pattern a lot e.g. for searching.) Because a subquery anyway results in more than one trip to the persistent store, you could just make one efficient fetch to evaluate the condition before setting the predicate for your fetched results controller.
After setting your predicate, remove it if necessary:
if ([context countForFetchRequest:request] == 0) {
request.predicate = nil;
}
// continue creating your FRC with the request
Note that countForFetchRequest is very efficient and takes up no memory.
To update,
self.fetchedResultsController = nil;
[self.tableView reloadData]; // or collection view
If you need to take advantage of the FRC's cache feature, a solution with two separate FRCs following the above pattern would also be feasible.
Thanks to the user Mundi, I was able to come up with a solution. First was to implement a method called getCorrectPredicate.
-(NSPredicate *)getCorrectPredicate{
NSPredicate *predicate1;
NSFetchRequest *fr;
predicate1 = [NSPredicate predicateWithFormat:
#"(SUBQUERY(hasFreds, $fred, $fred.hasEaten = %#).#count > 0)",#YES];
fr = [NSFetchRequest fetchRequestWithEntityName:#"Bob"];
fr.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO selector:#selector(compare:)]];
fr.predicate = predicate1;
if([[[self appDelegate] managedObjectContext] countForFetchRequest:fr error:nil] == 0){
// we have a none situation
self.predicateState = [NSPredicate predicateWithFormat:
#"hasFreds.#count=0"];
}else{
self.predicateState = predicate1;
}
return self.predicateState;
}
Second was to check in the NSFetchedResultsController delegate method controllerDidChangeContent whether or not the predicate changes.
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[_expensesTable endUpdates];
[[[self frc] fetchRequest] setPredicate:[self getCorrectPredicate]];
[self performFetch];
}

Split NSString into multiple entries to NSMutableDictionary as key/value?

Ok, so I have this problem I have been spinning my head around for some time now.
I have a NSString like the following:
NSString* foo = #"Brand: [Ford], Model: [Focus], Color: [black]";
which I would like to transfer into a NSMutableDictionary with Brand, Model, Color as keys and Ford, Focus, black as values (without the brackets [] ), but cannot seem to find any solution to this. How do I come about accomplishing the given scenario?
Edit:
Right now I use
NSArray *stringComponents = [foo componentsSeparatedByString#","];
which gives me an array like
stringComponents = [#"Brand: [Ford]",
#"Model: [Focus]",
#"Color: [black]",];
that I need to get into the syntax proposed by Sam, but how?
NSDictionary is basically just an array that stores the values of the objects and keys, so you'd do something such as:
NSArray *keys = [NSArray arrayWithObjects:#"Brand", #"Model", #"Color", nil];
NSArray *objects = [NSArray arrayWithObjects:#"Ford", #"Focus", #"black", nil];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
forKeys:keys];

NSFetchedResultsController predicate NOT filtering part of it (using ANY, IN and BETWEEN in the same fechRequest's predicate)

I've searched a lot here and couldn't find a solution to my situation:
I have a TableViewController that uses NSFetchedResultsController to display data from CoreData.
The model has an entity "Places" that has a to-many relationship called "Types" (and an inverse one, also to-many relationship).
In a first TableViewController I display the objects from entity "types" (each place can belong to more than one type, and one type can have more than one place). When the user taps on a row it calls a new TableViewController that will show objects from entity "Places" related to "Types" using a NSFetchedResultsController.
I know I could just use:
NSSet = [aType valueForKey:#"Places"];
However, I really want to use the NSFetchedResultsController and all its benefits.
Well, at the NSFetchedResultsController accessor method I was able to recreate this relationship by using:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Places"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSDictionary *types = [NSDictionary dictionaryWithObjectsAndKeys:self.theTypes, #"TYPES", nil];
NSPredicate *predicateAny = [NSPredicate
predicateWithFormat:#"ANY types IN $TYPES"];
NSPredicate *predicate = [predicateAny predicateWithSubstitutionVariables:types];
This code works fine as it returns "Places" that are related to the "Types" I want (those were hold inside the property theTypes). I use a property (theTypes), in the TableViewController to hold all "Types" objects one selected at the original tableViewController.
The problem is that the entity "Places" has a property named "distance" that I also need to use as a filter inside the NSPredicate, like this:
NSNumber *radious = [NSNumber numberWithDouble:10000.00];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"distance BETWEEN {0 , %#}", radious];
NSDictionary *types = [NSDictionary dictionaryWithObjectsAndKeys:self.theTypes, #"TYPES", nil];
NSPredicate *predicateAny = [NSPredicate
predicateWithFormat:#"ANY types IN $TYPES"];
NSPredicate *predicate = [predicateAny predicateWithSubstitutionVariables:types];
NSPredicate *thePred = [NSCompoundPredicate andPredicateWithSubpredicates:
[NSArray arrayWithObjects:predicate, pred, nil]];
[fetchRequest setPredicate:thePred];
Here's where the problem occurs: the "distance" filter appears to be simply ignored by the predicate at the fetchRequest. The resulted fetch always has Places that don't match the #"distance BETWEEN {0 , %#} clause.
Can anyone help me figure out what I am missing here?
Thanks a lot!
Daniel
This predicate:
NSPredicate *predicateAny = [NSPredicate
predicateWithFormat:#"ANY types IN $TYPES",
radious];
Doesn't make sense because you don't use the radious variable in this predicate. Not sure if that is the source of the problem but you should clean it up anyway.
This predicate:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"distance BETWEEN {0 , %#}", radious];
... could probably be:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"distance <=%#", radious];
... unless you have the possiblity of a negative radious value. It will be much faster that way.

CoreData, NSManagedObject fetch or create if not exists

I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...

Predicate Problem when fetching objects in a To-Many relationship

I have a simple situation where I have two entities related with Many-To-Many relationship.
Two objects, Alarms and Tags. When I want to fetch all the Alarms associated with a given Tag, I tried this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%# IN tags", theTag];
What I get is all Alarms, not just those related to the Tag.
However, trying this the other way around works:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF in %#", theTag.alarms];
For complicated reasons having to do with code reuse, I really need the first one to work. Any help would be much appreciated! Thanks!
If you have a Tag object, then you can get all of its alarms by doing:
NSSet *alarms = [theTag alarms];
If for some bizarre reason you have to do this with a fetch request (which you shouldn't), your predicate should be:
NSPredicate *p = [NSPredicate predicateWithFormat:#"tags CONTAINS %#", theTag];

Resources