问题
I am struggling to map the foreign key relationships returned by my JSON API with RestKit.
Specifically, I use Loopback to generate an API with Entities like Team
and User
. There are two Endpoints by default that return the following JSON:
/Teams/
[
{
"title": "Team Title",
"id": 1,
}
]
/Users/
[
{
"name": "Alice",
"id": 1,
"teamId": 1,
},
{
"name": "Bob",
"id": 2,
"teamId": 1,
}, ...
]
Now this is a pretty simple API, right? It should be easy to map with RestKit, but even after reading all those other questions about the topic (e.g. Seeking recommendations for best RestKit/CoreData mapping and JSON structure for shallow routes, Foreign key relationship mapping with RestKit, Restkit: GET remote linked object when foreign key refers to missing local object in Core Data) I just can't figure out how to load the entire object graph into core data.
If it makes things easier, I can alter the API as well, of course. For obvious reasons, I don't want to embed the entire Team
object in every User
but use a foreign key instead. Also, I want to keep the Endpoints separate, mostly because this is the way Loopback generates the API by default.
So what I tried is calling both Endpoints (in an order that should not matter) with the following mapping:
Team
Mapping
RKEntityMapping *teamMapping = [RKEntityMapping mappingForEntityForName:@"Team" inManagedObjectStore:objectManager.managedObjectStore];
[teamMapping addAttributeMappingsFromArray:@[ @"title", @"id" ]];
teamMapping.identificationAttributes = @[ @"id" ];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:teamMapping method:RKRequestMethodAny pathPattern:@"Teams" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
User
Mapping
RKEntityMapping *userMapping = [RKEntityMapping mappingForEntityForName:@"User" inManagedObjectStore:objectManager.managedObjectStore];
[userMapping addAttributeMappingsFromArray:@[ @"name", @"id", @"teamId" ]];
userMapping.identificationAttributes = @[ @"id" ];
[userMapping addConnectionForRelationship:@"team" connectedBy:@{ @"teamId": @"id" }];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodGET pathPattern:@"Users" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
As suggested in other questions (like the ones linked above), I use an RKConnectionDescription
to establish the relationship via a transient teamId
property. The mapping works fine with a JSON payload that includes both Team
and User
objects as arrays (with a keyPath instead of a pathPattern in the response descriptor), but fails with separate Endpoints that I call independantly from each other. In that case, the object the connection relationship refers to may not have been created yet. So to solve this, a Stub Object should be created with only the id
property populated in that situation.
How can I achieve this with a RestKit mapping?
Edit: Would it help if I changed the JSON structure from "teamId": 1
to "team": { "id": 1 }
and add another response descriptor with the keyPath team
? Not sure if that's easy to do with Loopback.
What's the best way to do this?
Edit 2: So I added the following stub mapping:
RKEntityMapping *teamStubMapping = [RKEntityMapping mappingForEntityForName:@"Team inManagedObjectStore:objectManager.managedObjectStore];
[teamStubMapping addAttributeMappingsFromDictionary:@{@"teamId": @"id"}];
teamStubMapping.identificationAttributes = @[ @"id" ];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:teamStubMapping method:RKRequestMethodAny pathPattern:@"Users" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
When I turn on trace logging, I don't see this mapping executed, though. What am I doing wrong? Is this mapping skipped because there is another one already matching the same pattern? When I comment out the User
mapping, it does its job.
Edit 3: So RestKit does actually use only the last response descriptor added to the object manager of those matching a given path and with equal key paths, as discussed here on GitHub. Thanks to the answer below, using a nil
key path mapping solves the issue:
RKEntityMapping *teamStubMapping = [RKEntityMapping mappingForEntityForName:@"Team inManagedObjectStore:objectManager.managedObjectStore];
[teamStubMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:@"id"]];
teamStubMapping.identificationAttributes = @[ @"id" ];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:teamStubMapping method:RKRequestMethodAny pathPattern:@"Users" keyPath:@"teamId" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
回答1:
I thought my below answer previously worked, but maybe I always snuck around the issue in the comments about exact matches of response descriptors.
So, create another mapping, but make it a nil keypath mapping and use a keypath on the response descriptor of @"teamId"
. This will cause RestKit to extract an array of team ids from the response and process them as object items, separate to the user objects.
The JSON is fine. Just create a new response descriptor which uses a teamStubMapping
, which is a new mapping like teamMapping
but replacing id
with teamId
. The path pattern of this response descriptor should be @"Users"
.
来源:https://stackoverflow.com/questions/24701657/restkit-creating-stubs-for-foreign-key-relationships