Sasha
Sasha

Reputation: 3445

Using NSFetchedResultsController with m:n relationships

I have a model that looks like this:

enter image description here

In code, I am enforcing that although an A can have multiple Bs, it has only one B per C.

What I want to do is list all the As for a given C, grouped by B. Now, this is fairly straight-forward if I start with my C, get the set of all Bs, and then get the set of all As for each B.

What I would like to do is list As using an NSFetchedResultsController. I can filter them by C using "ANY b.c MATCHES myC", but what I can't see how to do is to then group them by the correct B.

In other words, since an A has many Bs, how do I figure out which is the one that belongs to my C, and use that in the NSFetchedResultsController? Is that even possible?

Upvotes: 2

Views: 169

Answers (1)

Tommy
Tommy

Reputation: 100602

A cheap hack approach:

add a category method called something like nameOfRelevantB to object A, set myC as a global (or, probably, pass it to a class method on A that stores it to a file-local static) then set nameOfRelevantB as the sectionNameKeyPath on the NSFetchedResultsController.

nameOfRelevantB would find the B that matches the supplied C and return it.

The obvious disadvantage is that you're reduced to having one relevant C at a time.

You could ameliorate that by instigating a rule that in your app fetched results controllers have a one-to-one relationship with queues or threads and storing the relevant C as queue or thread context but then you'd need to write a manual UITableViewDataSource anyway in order to port the results back to the main queue/thread.

If you're going to write a custom data source you might as well make it an NSFetchedResultsControllerDelegate that breaks things down into sections of its own volition, avoiding the [file static] global.

A more thorough solution:

you could override valueForUndefinedKey: on your NSManagedObject subclass and put the relevant C's objectID directly into the key path. That's explicitly safe to do because managed object IDs have a URIRepresentation and NSURL provides absoluteString. Each A could then get the string URI from the key path, ask its context's persistent store coordinator for managedObjectIDForURIRepresentation and then ask the context for existingObjectWithID:error: in order to get to the relevant C. Once it has the C it can return the title from the appropriate B.

That'd achieve what you want without any type of global state. You'd also be using NSFetchedResultsController directly, giving it a key path from which it can determine sections.

So, e.g. (typed directly here, untested)

// get a URI for the relevant myC and prefix it with an '@'
// so that it's definitely clearly not an ordinary property
fetchedResults.sectionNameKeyPath = 
        [NSString stringWithFormat:@"@%@", 
              [[myC.objectID URIRepresentation] absoluteString]];

... in your subclass for A ...

- (id)valueForUndefinedKey:(NSString *)key
{
    // check that the key begins with the magic '@' symbol
    if(![key length] || [key characterAtIndex:0] != '@')
        return [super valueForUndefinedKey:key];

    // get the original URL
    NSString *URLString = [key substringFromIndex:1];
    NSURL *URL = [NSURL URLWithString:URLString];

    // transform the URL into the relevant instance of C
    NSManagedObjectID *objectID = [self.context.persistentStoreCoordinator
                  managedObjectIDForURIRepresentation:URL];
    NSError *error = nil;
    MyCClass *myC = [self.context existingObjectWithID:objectID error:&error];

    // check that we got an appropriate instance and didn't
    // generate an error
    if(!myC || error) return [super valueForUndefinedKey:key];

    /*
        code here to find the appropriate B and return a suitable title
    */
}

The main caveat is going to be that the object ID, and hence the URI, may change between the initial creation of an object and its first save. Provided you've already saved the context, the object ID will stay the same for as long as the object is in the store.

Upvotes: 1

Related Questions