I'm trying to use RestKit to map some JSON to my Core Data entities.
I have a Case entity with a caseId attribute and an optional one-to-many relationship to a Binder entity called binders. The Binder entity has an inverse one-to-one relationship to Case called case that's not optional.
I can successfully map the Case entities, but I'm having trouble with mapping the Binder entities.
My mappings are set up as follows:
RKEntityMapping *caseEntityMapping = [RKEntityMapping mappingForEntityForName:#"Case" inManagedObjectStore:managedObjectStore];
[caseEntityMapping addAttributeMappingsFromArray:[#"caseId"]];
RKEntityMapping *binderEntityMapping = [RKEntityMapping mappingForEntityForName:#"Binder" inManagedObjectStore:managedObjectStore];
NSString *pathPattern = #"rest/case/list";
[router.routeSet addRoute:[RKRoute routeWithName:#"getCases" pathPattern:pathPattern method:RKRequestMethodGET]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:caseEntityMapping pathPattern:pathPattern keyPath:#"data" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[_objectManager addResponseDescriptor:responseDescriptor];
pathPattern = #"rest/binder/list/:caseId";
[router.routeSet addRoute:[RKRoute routeWithRelationshipName:#"binders" objectClass:[PFCase class] pathPattern:pathPattern method:RKRequestMethodGET]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:binderEntityMapping pathPattern:pathPattern keyPath:#"data" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[_objectManager addResponseDescriptor:responseDescriptor];
I get the cases by using [_objectManager getObjectsAtPathForRouteNamed:#"getCases" object:nil parameters:nil success:… failure:…] and then inside the success block, I get the binders for each case by executing the request operation created using [_objectManager requestWithPathForRelationship:#"binders" ofObject:mappedCase method:RKRequestMethodGET parameters:nil].
But there are no mapping results for the binders. Am I missing something?
I had to add a foreign key RKConnectionDescription to the binder entity mapping, which required me to create a new attribute for the Binder entity called caseId.
Is there any way to do this without having to create a new attribute? Also, what is the key path RKConnectionDescription for/when would it be needed?
Related
I'm attempting to use RestKit to handle posting an updated User object to my remote web service.
Currently my GET requests seem to be working fine but I'm having issues using
[[RKObjectManager sharedManager] postObject:updatedUser path:#"path" parameters:nil success:nil failure:nil];
Invoking this method is throwing a EXC_BAD_ACCESS exception.
My mappings are set up as follows, I believe I have both the RKRequestDescriptor and RKResponseDescriptor's.
User Response Mapping:
RKEntityMapping * userMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([User class])
inManagedObjectStore:[manager managedObjectStore]];
….Setup mapping (I excluded a relationship mapping on this object)
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:userMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"currentUser"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]]
Request mapping:
[manager addRequestDescriptorsFromArray:#[
[RKRequestDescriptor requestDescriptorWithMapping:[userMapping inverseMapping]
objectClass:[User class]
rootKeyPath:nil
method:RKRequestMethodPOST]]];
The mappings seem to set up fine, the EXC_BAD_ACCESS exception is thrown when I call postObject
The test method looks like this, _updatedUser is a CoreData object fetched using [RKObjectManager sharedManager] getObjectsAtPath:…
-(void) doPost{
//this user is a CoreData object fetched using
[_updatedUser setBio:#"This is an update!"];
RKObjectManager * objectManager = [RKObjectManager sharedManager];
[objectManager postObject:_updatedUser
path:#"update/user"
parameters:nil
success:…
failure:…];
}
I've attempted using NSZombies to find the cause of this but I have't had much luck.
From what I can tell the start of the issue seems to be coming from RKObjectParameterization's -[RKObjectParameterization mappingOperation:didSetValue:forKeyPath:usingMapping:] where it looks like everything passed into the method is nil or an empty string.
Thanks!
Much thanks to Wain, after spending way too much time on this the error became instantly apparent after I turned on logging:
RKLogConfigureByName("RestKit", RKLogLevelWarning);
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
It turns out I had a circular reference between mapped objects.
I have a one to one relationship where a User contains a UserProfile
I incorrectly set up a bidirectional relationship mapping between User and UserProfile
[userProfileMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"user"
toKeyPath:#"user"
withMapping:userMapping]];
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"userProfile"
toKeyPath:#"userProfile"
withMapping:userProfileMapping]];
It looks like my endless loop was caused by userProfileMapping
Thanks Wain, logging lesson learned.
I have an entity Comments. When I perform a rest operation, i get a response which has fields such as
{
"status": "succeed"
}
I want to process these fields to know if the operation was successful or not but I dont want to add status to comment class because it doesnt belogn there.
RKObjectManager *sharedRKObjectManager = [RKObjectManager sharedManager];
RKManagedObjectStore *managedObjectStore = [sharedRKObjectManager managedObjectStore];
// Create a mapping for the comment entity
RKEntityMapping *responseMapping = [RKEntityMapping mappingForEntityForName:ENTITY_COMMENT inManagedObjectStore:managedObjectStore];
[responseMapping addAttributeMappingsFromDictionary:#{
#"comment_id": #"commentId"
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
method:RKRequestMethodAny
pathPattern:COMMENT
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
Whats the best way to do this?
Just use a plain RKObjectMapping to a custom class or an NSDictionary and with the single status key. Then you should get a simple single item in the mapping result.
I'll try to explain how I want the mapping done:
TOP lvl json object contains Players object which is an array of Player objects
each Player object contains an array, I want each of those objects in the array to be of an Event object (custom object).
now since I have a mapping of the Player object and i'm getting the array filled, tho instead of Event objects(which is what I want), i'm getting NSDictionary objects. thing is that I do have a mapping of my Event class. my issue is getting the RestKit to map these into the array.
I've tried adding responseDescriptors of an Event class tho i've had no luck.
Here is the Player object mapping
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
...more here
#"activeEvents" : #"activeEvents"
}];
here is the request method
NSURL *taskURL = [NSURL URLWithString:kAppWebApiURLPath];
// Set object manager with base url
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager = [RKObjectManager managerWithBaseURL:taskURL];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager.HTTPClient setDefaultHeader:#"Authorization" value:kAppWebAPIKey];
[objectManager.HTTPClient setDefaultHeader:#"Content-Type" value:#"application/json"];
RKRequestDescriptor * requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[MappingProvider inverseLoginMapping] objectClass:[LoginInfo class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:requestDescriptor];
RKResponseDescriptor *playersResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider playerMapping] method:RKRequestMethodGET pathPattern:nil keyPath:#"players" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:playersResponseDescriptor];
NSLog(#"%#",loginInfo.iOSDeviceToken);
[objectManager postObject:loginInfo path:kAppWebApiLoginPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{}...
Update, I now need one step further of mapping, My player object contains an array of events which I successfully mapped using
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:[MappingProvider eventMapping]];
yet now each of those Event objects contains an array of Players, so its like Players -> Events -> Players.
Here is the Mapping for both Event and Player objects :
RKObjectMapping* eventMapping = [RKObjectMapping mappingForClass:[Event class]];
[eventMapping addAttributeMappingsFromDictionary:#{
#"stuffhere" : #"stuffz"
}];
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"activeEvents" : #"activeEvents"
}];
[eventMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents/players" mapping:playerMapping];
now I don't get a recursive function, but how do I state in code to make that relationship
mapping of the json array to assign to my local array property ?
Remove #"activeEvents" : #"activeEvents" from the mapping and replace it with:
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:eventMapping];
You should also only have one response descriptor because the data is nested.
I have the following JSON returned from my restkit call:
{"test":{"123423":{"id":"123423","type":"mood","stress":null}}}
and I am using the following mapping
RKObjectMapping *testResponseMapping = [RKObjectMapping mappingForClass:[TestDataResponse class]];
[testResponseMapping addAttributeMappingToKeyOfRepresentationFromAttribute:#"itemNumber"];
[testResponseMapping addAttributeMappingsFromDictionary:#{
#"(itemNumber).id":#"id",
#"(itemNumber).type":#"type",
#"(itemNumber).stress":#"stress"
}];
RKResponseDescriptor *testResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:testResponseMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"test" statusCodes:[NSIndexSet indexSetWithIndex:200]];
This is not quite right as I'm not sure I am handling the first "123423" (which is a number that changes) correctly.
How do I update my code to accommodate this properly? Thanks!
I have used common RKObjectManager used for different entity mapping as the following below cod but when i try to make mapping for specific entity couldn't because i have two entity with same keyPath this the problem how i can figured.
// Search mapping ...
RKEntityMapping *searchEntityMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([ABB class]) inManagedObjectStore: aBBManager.managedObjectStore];
[searchInfoEntityMapping addAttributeMappingsFromDictionary:#{
#"count" : #"count",
#"total_count" : #"totalCount",
}];
// Search Advanced mapping ...
RKEntityMapping *searchAdvEntityMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([ABB class]) inManagedObjectStore: aBBManager.managedObjectStore];
[searchAdvEntityMapping addAttributeMappingsFromDictionary:#{
#"count" : #"count",
#"data" : #"dataCount",
}];
// Search Descriptor
RKResponseDescriptor *aBBResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:searchEntityMapping pathPattern:nil keyPath:#"locations" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
// Search Adv Descriptor
RKResponseDescriptor *aBB2ResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:searchAdvEntityMapping pathPattern:nil keyPath:#"locations" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
You should use the pathPattern parameter to allow RestKit to know which response descriptor to use when you make a particular request (because you should be using different paths in the URLs for different entities).
If for some reason you can't, you would need to create multiple instances of RKObjectManager and use the appropriate one for each different request that you make.