Relationship mapping in RestKit using an array of identifiers does not work

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:///User/t01A9EDBD-A523-4806-AFC2-9B06873D764E531> ; data: { fullname = nil; identifier = nil; }) 

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.

+4
source share
1 answer

A way was found to make this work, and also realized that the updated approach in my question is supposed to be used only for nested objects, and not for references to objects. This thread gave me the correct path: https://groups.google.com/forum/#!msg/restkit/swB1Akv2lTE/mnP2OMSqElwJ (worth reading if you are dealing with relationships in RestKit)

Suppose the JSON groups still look like the original JSON:

 {"groups": [{"name": "John group", "_id": "a", "members": ["1", "2"] ... ] } 

Also suppose that the users are mapped to the users key (that is, something like [objectManager.mappingProvider setMapping:userMapping forKeyPath:@"users"]; ) the correct way to map relationships is as follows:

 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"]; [groupMapping mapKeyPath:@"users" toRelationship:@"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:@"users"] serialize:NO]; [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 tried this approach before, what confused me was this line

 [groupMapping mapKeyPath:@"users" toRelationship:@"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:@"users"] serialize:NO]; 

where the first key path refers to the mapping of the key path for the reference objects ( users in this case), and not the key path of the relationship within the group object that is displayed (which would be members in this case).

With this change, all relationships work as expected, and I'm happy.

+2
source

Source: https://habr.com/ru/post/1441866/


All Articles