3

I reckon I'm probably missing something obvious here, as this has got to be a pretty common use case for RestKit.

I want to have two view controllers, each just glueing an NSFetchedResultsController to a UITableView, and let's say that the first one displays a timeline of posts, and the second one displays a list of posts for a certain user. I need different lists of posts for each of these view controllers, but I can't work out how to get these lists using RestKit.

Currently, I use the same NSManagedObjectContext on each view and that means that if I have a post that exists in view controller B but not in view controller A, if I go back to view controller A after loading view controller B, that post that should be unique to view controller B now also shows up in view controller A.

I thought what I should do was use different NSManagedObjectContexts for each view, sharing an RKManagedObjectStore but I couldn't work out how to get this to work.

These are my mappings:

RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:@"User" inManagedObjectStore:managedObjectStore];
[userMapping addAttributeMappingsFromDictionary:@{
    @"avatar_url": @"avatarURL",
    @"id":         @"userID",
    @"first_name": @"firstName",
    @"last_name":  @"lastName",
    @"username":   @"username"
}];
userMapping.identificationAttributes = @[@"userID"];

RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
[postMapping addAttributeMappingsFromDictionary:@{
    @"id":          @"postID",
    @"content_url": @"contentURL",
    @"created_at":  @"createdAt"
}];
postMapping.identificationAttributes = @[@"postID"];
[postMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"creator" toKeyPath:@"creator" withMapping:userMapping]];

[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"posts" toKeyPath:@"posts" withMapping:postMapping]];

[objectManager.router.routeSet addRoute:[RKRoute routeWithClass:[Post class] pathPattern:@"/posts\\.json" method:RKRequestMethodGET]];
[objectManager.router.routeSet addRoute:[RKRoute routeWithClass:[Post class] pathPattern:@"/posts\\.json" method:RKRequestMethodPOST]];

[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:postMapping
                                                                             pathPattern:@"/posts.json"
                                                                                 keyPath:nil
                                                                             statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];

[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:userMapping
                                                                             pathPattern:@"/_/:username.json"
                                                                                 keyPath:nil
                                                                             statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];

These are my Core Data entities

(StackOverflow won't let me post images…)

In my view controllers, this is my fetchedResultsController getter

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"createdAt" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];

    [fetchRequest setSortDescriptors:sortDescriptors];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _fetchedResultsController;
}

This is how I make the request from the timeline view controller

[[RKObjectManager sharedManager] getObjectsAtPath:@"/posts.json" parameters:nil success:success failure:failure];
// The success and failure blocks don't do all that much, so I've just left them out.

This is how I make the request for the user view controller

NSString *path = [NSString stringWithFormat:@"/_/%@.json", self.user.username];
[[RKObjectManager sharedManager] getObjectsAtPath:path parameters:nil success:success failure:failure];
Alyssa Ross
  • 2,169
  • 16
  • 26

2 Answers2

1

First off, don't start path patterns with a /. You also don't need it at the start of paths in requests.

For the actual problem, when you define your NSFetchedResultsController you also need to define an NSPredicate for the fetchRequest. This predicate will allow the FRC to filter to only the information that the view controller is actually interested in (even though your data model actually contains more data than that). The predicate will be based on some value that the controller is passed that it should display details for...

Check the predicate docs here.

Wain
  • 118,658
  • 15
  • 128
  • 151
0

@Ross, what your are doing wrong is that you do not filter your request, you just get all of them, you must use NSPredicate to filter it.

You will need to have differente filters in your view controller A and View controller B.

You can create something like that for example to get all posts from a user:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"creator.username like %@",
       self.user.username];

To get all the last posts, you don't even need a predicate, your sorte descriptor must solve the problem.

This is how your method will look like when you need to sent a predicate to it, or you can add directly it inside your method that you already have. Now it is your choice.

- (NSFetchedResultsController *)fetchedResultsController:(NSPredicate*)predicate
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    [fetchRequest setFetchBatchSize:20];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"createdAt" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];

    [fetchRequest setSortDescriptors:sortDescriptors];

    [fetchRequest setPredicate:predicate];

    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _fetchedResultsController;
}

EDIT Fot the option 3 -You can have a list of Users that are followed and filter your request with the users, and get all the posts of the users that you follow.

You will need to get first all users that are followed and then you can evaluate your posts with a subquery that will look like that (I did not tested it, maybe something is wrong)

[NSPredicate predicateWithFormat:@"(SUBQUERY(creator, $x, $x.username IN %@).@count > 0)", followedUsernames];

Where followedUsernames are an array with all the usernames of the followed users.

ggrana
  • 2,335
  • 2
  • 21
  • 31
  • Thanks @ggrana. I see how I would filter in a the user view controller, but I don't see how I would filter in the timeline view controller. There is nothing there that identifies the posts, they would only appear if the user followed the creator. Could I do that using NSPredicate? – Alyssa Ross May 23 '13 at 17:37
  • I do not know the purpose of your project or what you can change on it, but you have some options. 1 - you can add a field to identify the posts that must show in the first view controller. 2 - You can create separated models. 3 -You can have a list of Users that are followed and filter your request with the users, and get all the posts of the users that you follow. – ggrana May 23 '13 at 17:46