I am trying to map users and groups to CoreData objects through RestKit, keeping in touch.
JSON for users is something like
{"users": [{"fullname": "John Doe", "_id": "1"} ... ] }
and JSON for groups is something like
{"groups": [{"name": "John group", "_id": "a", "members": ["1", "2"] ... ] }
I also have corresponding Core Data, User and Group objects, with a one-to-many relationship between groups and users. The relationship property in Group is called members , and a separate NSArray called memberIDs contains an array of row identifiers for all member users.
What I want to do in RestKit is to load these objects and match the mappings for me. The code to load users is direct standard content, and the code to load groups is something like
RKObjectManager* objectManager = [RKObjectManager sharedManager]; RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore]; groupMapping.primaryKeyAttribute = @"identifier"; [groupMapping mapKeyPath:@"_id" toAttribute:@"identifier"]; [groupMapping mapKeyPath:@"name" toAttribute:@"name"]; [groupMapping mapKeyPath:@"members" toAttribute:@"memberIDs"]; [objectManager.mappingProvider setMapping:groupMapping forKeyPath:@"groups"]; // Create an empty user mapping to handle the relationship mapping RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class] inManagedObjectStore:objectManager.objectStore]; [groupMapping hasMany:@"members" withMapping:userMapping]; [groupMapping connectRelationship:@"members" withObjectForPrimaryKeyAttribute:@"memberIDs"]; RKObjectRouter* router = objectManager.router; [router routeClass:[Group class] toResourcePath:@"/rest/groups/:identifier"]; [router routeClass:[Group class] toResourcePath:@"/rest/groups/" forMethod:RKRequestMethodPOST]; // Assume url is properly defined to point to the right path... [[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
When I run this code, I get the following warning, which spits out several times (it seems about two times for a relationship):
2012-10-24 08:55:10.170 Test[35023:18503] W restkit.core_data.cache:RKEntityByAttributeCache.m:205 Unable to add object with nil value for attribute 'identifier': <User: 0x86f7fc0> (entity: User; id: 0x8652400 <x-coredata:
Itβs strange that the relations are being established properly (they even looked at the sqlite database, and everything looks fine there), but I am not happy that something is clearly wrong in the RestKit code, and there seems to be something to do with fake the user mapping that I create in the above code (it is empty since there is nothing in the identifier array).
I tried alternatives, for example, when I add key mapping:
[userMapping mapKeyPath:@"" toAttribute:@"identifier"];
He complains, obviously, because the key path is empty
2012-10-24 09:24:11.735 Test[35399:14003] !! Uncaught exception !! [<__NSCFString 0xa57f460> valueForUndefinedKey:]: this class is not key value coding-compliant for the key .
And if I try to use a real mapping User
[groupMapping hasMany:@"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:@"users"]];
I get the following error
2012-10-24 09:52:49.973 Test[35799:18403] !! Uncaught exception !! [<__NSCFString 0xa56e9a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id. 2012-10-24 09:52:49.973 Test[35799:18403] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0xa56e9a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id.'
RestKit seems to be trying to match the ID strings in the memberIDs array to User objects, which makes no sense. I saw examples when an array of relations is a list of dictionaries with keys (for example, called id ) with the identifier of a link to another object, for example:
{"groups": [{"name": "John group", "_id": "a", "members": [ {"_id": "1"}, {"_id": "2"} ] ... ] }
But I donβt want to change my JSON this way (besides, I hope that RestKit is flexible enough to support another way.)
Does anyone know what the correct way to do this relationship mapping in RestKit?
Update
I finished modifying the REST interface to send a list of dictionaries containing user object identifiers (like the last example above), and got this to work. Still not quite happy with the setup, but now the code looks like
RKObjectManager* objectManager = [RKObjectManager sharedManager]; RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore]; groupMapping.primaryKeyAttribute = @"identifier"; [groupMapping mapKeyPath:@"_id" toAttribute:@"identifier"]; [groupMapping mapKeyPath:@"name" toAttribute:@"name"]; [groupMapping mapKeyPath:@"members._id" toAttribute:@"memberIDs"]; [objectManager.mappingProvider setMapping:groupMapping forKeyPath:@"groups"]; // Create an empty user mapping to handle the relationship mapping RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class] inManagedObjectStore:objectManager.objectStore]; userMapping.primaryKeyAttribute = @"identifier"; [userMapping mapKeyPath:@"_id" toAttribute:@"identifier"]; [groupMapping hasMany:@"members" withMapping:userMapping]; [groupMapping connectRelationship:@"members" withObjectForPrimaryKeyAttribute:@"memberIDs"]; RKObjectRouter* router = objectManager.router; [router routeClass:[Group class] toResourcePath:@"/rest/groups/:identifier"]; [router routeClass:[Group class] toResourcePath:@"/rest/groups/" forMethod:RKRequestMethodPOST]; // Assume url is properly defined to point to the right path... [[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
Just in case, this helps someone else in a similar situation. Perhaps there may still be some problems with the approach I took, but still so good (it even handles loading the order, which is nice) - it will still be nice to hear from someone if there is an answer to my initial question.