8

I am having trouble mapping relationships when the JSON response only contains the primary key and not a fully nested array to create the new object.

I have 2 classes - Shop and Item, and as you would expect the Shop->Item has a one-to-many relationship.

I have a local core-data store of shops (and items), each with a primary key. I then wish to download a list of Items as JSON and map to core-data entities, but only include the primary key of the shop, and not all the shop details as a nested array - this would be huge waste of network traffic as I am downloading details of 500+ Items.

Here is the JSON from the two requests:

/shops

{
    "id" : 1,
    "shop" : "Shop A",
    "city" : "New York"
},
{
    "id" : 2,
    "shop" : "Shop B",
    "city" : "London"
},
...

/items

{
    "id" : 1,
    "name" : "Shoes",
    "manufacturer" : "Vans",
    "shopId" : 1
},
{
    "id" : 2,
    "name" : "T-shirt",
    "manufacturer" : "Animal",
    "shopId" : 2
},
{
    "id" : 3,
    "name" : "Scarf",
    "manufacturer" : "Ted Baker",
    "shopId" : 1
},
{
    "id" : 4,
    "name" : "Sunglasses",
    "manufacturer" : "Ray-Ban",
    "shopId" : 3
},
...

Here is my code at the moment.

AppDelegate.m

...

NSURL *baseURL = [NSURL URLWithString:@"http://localhost/company/API"];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseURL];

[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;

[objectManager.HTTPClient setDefaultHeader:@"Accept" value:@"application/json"];

NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;

// Shop Mapping

RKEntityMapping *shopMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Shop class])
                                                       inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *shopMappingAttributes = [NSDictionary dictionaryWithObjectsAndKeys:@"objectId",@"id",@"name",@"shop",@"city",@"city",nil];
shopMapping.identificationAttributes = @[@"objectId"];
[shopMapping addAttributeMappingsFromDictionary:shopMappingAttributes];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:shopMapping
                                                                             pathPattern:@"/shops"
                                                                                 keyPath:nil
                                                                             statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];


// Item Mapping

RKEntityMapping *itemMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Item class])
                                                       inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *itemMappingAttributes = [NSDictionary dictionaryWithObjectsAndKeys:@"objectId",@"id",@"name", @"name",@"manufacturer",@"manufacturer",nil];
itemMapping.identificationAttributes = @[@"objectId"];
[itemMapping addAttributeMappingsFromDictionary:itemMappingAttributes];

// Define the relationship mapping

[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:itemMapping
                                                                             pathPattern:@"/items"
                                                                                 keyPath:nil
                                                                             statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];

...

ItemsTableViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];

    // Update Shops
    [[RKObjectManager sharedManager] getObjectsAtPath:@"/shops"
                                       parameters:nil
                                          success:nil
                                          failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                              NSLog(@"Error: %@",error);
                                          }];

    // Update/Get Items
    NSDictionary *parameters = @{
                             @"username": self.username,
                             @"password": self.password,
                             @"API_key": @"abc123",
                             };

    NSMutableURLRequest *request = [[RKObjectManager sharedManager] requestWithObject:nil
                                                                           method:RKRequestMethodPOST
                                                                             path:@"/items"
                                                                       parameters:parameters];

    RKManagedObjectRequestOperation *operation = [[RKObjectManager sharedManager] managedObjectRequestOperationWithRequest:request managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext
                                                                  success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                                                      Item *item = [mappingResult firstObject];
                                                                      NSLog(@"Mapped the Item: %@", item);
                                                                  } failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                                                      NSLog(@"Error: %@",error);
                                                                  }];
    NSOperationQueue *operationQueue = [NSOperationQueue new];
    [operationQueue addOperation:operation];
}

EDIT: Wain, I have this in the relevant place in the app delegate but get an NSException

NSEntityDescription *itemEntity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
NSRelationshipDescription *shopRelationship = [itemEntity relationshipsByName][@"shop"];
RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:shopRelationship attributes:@{ @"shopId": @"objectId" }];
[itemMapping addConnection:connection];

NSException

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Item''

What have I missed?

veducm
  • 5,933
  • 2
  • 34
  • 40
Andy
  • 717
  • 1
  • 9
  • 24

1 Answers1

5

You need to add a transient attribute to the item (called shopId) and an associated mapping.

Configure the relationship using foreign key mapping as:

NSEntityDescription *itemEntity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext];
NSRelationshipDescription *shopRelationship = [itemEntity relationshipsByName][@"shop"];
RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:shopRelationship attributes:@{ @"shopId": @"id" }];

Then use addConnection: to add it to your mapping.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • thanks @Wain. Would this snippet of code go in the App Delegate or the View Controller? Andy – Andy Sep 21 '13 at 18:04
  • Add it to `itemMapping` where you previously tried to add a relationship mapping (but for a nested item). – Wain Sep 21 '13 at 18:13
  • I'm getting an exception - see edit. Is this because I haven't created transient attribute on Item? If so how? Andy – Andy Sep 21 '13 at 18:35
  • I don't know your entity name, you need to fact check the code, I guessed the likely name. – Wain Sep 21 '13 at 19:09
  • Why is the transient needed... a relation would be sufficient right? Would the shopId just return that relation? – tapmonkey Feb 12 '14 at 17:13
  • @tapmonkey the transient holds the id used in the foreign key mapping process – Wain Feb 12 '14 at 17:23
  • @Wain Does it matter, if the mapping is configured using the mainManagedObjectContext, but the RKManagedObjectRequestOperation will be performed using on other context? – Balazs Nemeth Feb 25 '16 at 15:08
  • you don't configure a mapping with a context, and this comment appears to have nothing to do with this question / answer... perhaps you just mean the entity description used, in which case, no – Wain Feb 25 '16 at 15:12