I'm sending out a request to get an object with an nested array of data. However, RestKit is only saving the last item in the nested array of data.
For example, here's the JSON response the server is delivering
{
"nutrition_day": {
"id": "5342e9e13138610012420100",
"start_on": "2014-04-07",
"meal_goals": [
{
"id": "5342e9e13138610012410100",
"name": "Breakfast",
"food_entries": [
{
"id": "535429513663320008210000",
"consumed_on": "2014-04-20",
"food_id": 33801,
"serving_id": 29457,
}
]
},
{
"id": "5342e9e13138610012430100",
"name": "Morning Snack",
"food_entries": []
}
]
}
}
However, using the code below, when I attempt to access nutrition_data.meal_goals, I am left with only the last item in the array "Morning Snack" and am not getting "Breakfast"
Mapping:
+ (void)mapping
{
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
RKEntityMapping *responseMapping = [RKEntityMapping mappingForEntityForName:#"NutritionDay" inManagedObjectStore:[[RKObjectManager sharedManager] managedObjectStore]];
[requestMapping addAttributeMappingsFromArray:#[#"date"]];
[responseMapping addAttributeMappingsFromArray:#[#"start_on", #"protein_target"]];
[responseMapping setIdentificationAttributes:#[#"start_on"]];
[responseMapping addRelationshipMappingWithSourceKeyPath:#"meal_goals" mapping:[MealGoal responseMapping]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:[NutritionDay class]
rootKeyPath:#"nutrition_day"
method:RKRequestMethodGET];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
method:RKRequestMethodAny
pathPattern:#"nutrition/days/:date"
keyPath:#"nutrition_day"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
}
Request:
[[RKObjectManager sharedManager] getObjectsAtPath:#"nutrition/days/2014-12-12" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NutritionDay *nut = ((NutritionDay *)mappingResult.firstObject);
NSLog(#"%lu", (long)nut.meal_goals.count); // this always returns 1 when it should be 2
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"fail");
}];
Is there something I need to do so that RestKit recognizes the entire array of the relationship and saves it?
I would suggest that you should include the id in your mapping and use that as the identification attribute.
meal_goals should be a to-many relationship (with inverse).
Your log should be:
NSLog(#"%lu", (long)nut.meal_goals.count);
Other than those things, the code you show looks correct. Log the entire mapping result to verify the contents.
Related
Am i doing something weired here ?
my categories get downloaded and mapped,
product also get downloaded and mapped as logging is saying,
but my products are empty under each category,
thnx!
{
"productsCategories" : [
{
"product" : [
{
"price" : 3.99,
"title" : "Product A"
},
{
"price" : 3.99,
"title" : "ProductB "
}
],
"categoryTitle" : “a category“
}
]
}
RKObjectMapping *productCategoryMapping = [RKObjectMapping mappingForClass:[ProductCategory class]];
[productCategoryMapping addAttributeMappingsFromDictionary:#{
#"categoryTitle": #"tit"
}];
RKObjectMapping* productMapping = [RKObjectMapping mappingForClass:[Product class] ];
[productMapping addAttributeMappingsFromDictionary:#{
#"title": #"tit",
#"price": #"prc"
}];
[productCategoryMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"product"
toKeyPath:#"product"
withMapping:productMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:productCategoryMapping method:RKRequestMethodPOST pathPattern:#"productByCategory" keyPath:#"productsCategories" statusCodes:[NSIndexSet indexSetWithIndex:200]];
NSURL *url=[NSURL URLWithString:#"http://www.example.com/REST/v2/"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:url];
[manager addResponseDescriptor:responseDescriptor];
NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionary];
[mutableParameters setValue:#"1" forKey:#"id"];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:productCategoryMapping method:RKRequestMethodPOST pathPattern:#"productByCategory" keyPath:#"productsCategories" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
[manager postObject:request path:#"productByCategory" parameters:mutableParameters success:^(RKObjectRequestOperation *operation, RKMappingResult *result){
self.productCategoryArr=result.array;
[self.tblDetails reloadData];
}failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
self.productCategoryArr=nil;
}];
the logging says objects are being mapped for each products but I only get
ProductCategory: 0x7bf53510
ProductCategory: 0x7be57f00
arrays and 0 objects in each
Assuming your ProductCategory class has
NSArray *Product
NSString * tit
Create a top level RKObjectMapping like,
RKObjectMapping * ProductCategoryResponseMapping = [RKObjectMapping mappingForClass: [ProductCategoryResponse class]];
[ProductCategoryResponseMapping addAttributeMappingsFromDictionary:#{
#"productsCategories": #"productsCategories"
}];
And create new class ProductCategoryResponse which should have
NSArray * productsCategories
Use ProductCategoryResponseMapping for Response descriptor.
Now your response will have array of productsCategories, each having array of Products and String tit.
I have a weird issue with restkit mapping to CoreData. All works great in simulator but not in real device. After successful request to server, mapping of results is successful in both cases (i can access every value in RKMappingResult *mappingResult ). New values are stored in simulator but they arent stored in device. I did a lot of googling but without solution.
I do restkit requst this way:
RKObjectMapping *requestMapping = [[hcUpdateRequest defineUpdateRequestMapping] inverseMapping];
[self.objectManager addRequestDescriptor: [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[hcUpdateRequest class] rootKeyPath:nil method:RKRequestMethodPOST]];
RKEntityMapping *storyMapping = [hcUpdateResponse storyMapping:self.objectManager.managedObjectStore];
{..moreMapping..}
[storyMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"pieces" toKeyPath:#"pieces" withMapping:pieceMapping]];
{..moreMapping..}
[self.objectManager setRequestSerializationMIMEType: RKMIMETypeFormURLEncoded];
[self.objectManager postObject:self.dataUpdateRequst
path:#"storyAll"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {…}
+ (RKEntityMapping*) storyMapping:(RKManagedObjectStore*)managedObjectStore {
RKEntityMapping* storyMapping = [RKEntityMapping mappingForEntityForName:#"Story" inManagedObjectStore:managedObjectStore];
[storyMapping addAttributeMappingsFromDictionary:#{
#"id":#"idRemoteDatabase",
#"name":#"name",
#"position_x": #"positionX",
#"position_y": #"positionY",
#"state": #"state",
#"gender": #"gender"
}];
storyMapping.identificationAttributes = #[#"idRemoteDatabase"];
return storyMapping;
}
I have used RestKit and made a mapping with a managed object. I then use the postObject method but I have a problem when retrieved the body as it maps to null.
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// Serialize to JSON
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[RKObjectManager setSharedManager:objectManager];
RKEntityMapping *searchInfoMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Search class]) inManagedObjectStore: objectManager.managedObjectStore];
searchInfoMapping.identificationAttributes = #[ #"faresQueryType" ];
[searchInfoMapping addAttributeMappingsFromDictionary:#{
#"id" : #"ID",
#"type" : #"Type",
#"count" : #"Count",
#"route” : #"route”,
}];
RKObjectMapping *searchInfoRequestMapping =[RKObjectMapping requestMapping];
[searchInfoRequestMapping addAttributeMappingsFromDictionary:#{
#"id" : #"ID",
#"type" : #"Type",
#"count" : #"Count",
#"route” : #"route”,
}];
//Data mapping is a method that returns an RKObjectMapping for my model
RKResponseDescriptor * searchDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:searchInfoMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsFromArray:#[searchDescriptor,]];
//Inverse mapping, to perform a POST
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:searchInfoRequestMapping objectClass:[Search class] rootKeyPath:nil];
[objectManager addRequestDescriptor:requestDescriptor];
[appDelegate.objectManager postObject:nil path:#"routes" parameters:postParameters success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success case %#",mappingResult);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure in Flight Mapping %#",error);
[alert show];
}];
Edit
//JSON Result
{
"currency": {
"code": "USD",
"name": "U.S. Dollar",
"symbol": "US$",
"exchange_rate": 1
},
"routes": [
{
"id": "MS648-2022:MS985-2110",
"fares": [
{
"price": 745.32,
"description": "Economy",
"deeplink": "http://www.wego.com/flights/providers/2/deeplinks?search_id=CQtxCXQCRfmwhp72PAjopQ&trip_id=RUH:NYC:2013-12-20&fare_id=sky-tours.com:0&route=RUH-JFK",
"provider_code": "sky-tours.com",
"deeplink_params": {
"trip_id": "RUH:NYC:2013-12-20",
"route": "RUH-JFK",
"search_id": "CQtxCXQCRfmwhp72PAjopQ",
"fare_id": "sky-tours.com:0"
}
}
]
}
]
}
You need to supply the Search object instead of nil when you call postObject (currently you have postObject:nil). This tells RestKit which request mapping to use to send the request. As a result of not setting it the server will likely not have all of the required information (it's impossible to tell with the information provided).
I am using Restkit .20 and trying to get the error object returned in a failure block. I have tried both below and both return (null) for the error object.
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:
[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorMessage"]];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
pathPattern:nil
keyPath:#"error" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[self.objectManager addResponseDescriptorsFromArray:#[errorDescriptor]];
AND
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"error" toKeyPath:#"errorMessage"]];
[self.objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:#"error" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]];
This is in my failure block which prints out "errorMessage: (null)"
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
NSLog(#"errorMessage: %#", [[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey]);
}
Here is what is return from in the JSON from the server.
{
"status": "FAIL",
"user_errors": null,
"error": "Credit card type is not accepted by this merchant account.",
"user": null
}
An HTTP status code 201 is a success code, not an error code so your error mapping will not be tried (because of statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)).
So, update the server to send an appropriate status code. If you update the response descriptor to check 'successful' responses for errors I think it will work.
I'm struggling a bit with RestKit and CoreData, especially since there are so little examples and documentation out there for RestKit 0.20.
I have an (managed) object Song with a many-to-one relationship with Album. The following code can post JSON, but not in the flattened format, which the server excepts.
// Defined elsewhere
Album *theAlbum;
RKObjectManager *objMan = [self objectManager];
// Response Mapping
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[Song class]];
[responseMapping addAttributeMappingsFromDictionary:#{ #"song": #"songID" }];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping
pathPattern:#"/api/song"
keyPath:nil
statusCodes:statusCodes];
// Request Mapping
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
RKEntityMapping *albumRelationshipMapping = [RKEntityMapping mappingForEntityForName:#"Album" inManagedObjectStore:[objMan managedObjectStore]];
[albumRelationshipMapping addAttributeMappingsFromDictionary:#{#"id": #"albumID", }];
[requestMapping addAttributeMappingsFromDictionary:#{ #"title": #"title", #"length": #"length" }];
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"album"
toKeyPath:#"album"
withMapping:albumRelationshipMapping]];
requestMapping = [requestMapping inverseMapping];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[Song class] rootKeyPath:nil];
[objMan addRequestDescriptor:requestDescriptor];
[objMan addResponseDescriptor:responseDescriptor];
// Create a new temporary song object
Song *song = [NSEntityDescription insertNewObjectForEntityForName:#"Song"
inManagedObjectContext:[[objMan managedObjectStore] mainQueueManagedObjectContext]];
song.title = #"Some Title";
song.length = 123;
song.album = theAlbum;
// Post operation
objMan.requestSerializationMIMEType = RKMIMETypeJSON;
RKManagedObjectRequestOperation *operation = [objMan appropriateObjectRequestOperationWithObject:song
method:RKRequestMethodPOST
path:#"/api/song"
parameters:nil];
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// Success
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Failure
}];
[objMan enqueueObjectRequestOperation:operation];
This code will post a JSON body like this:
{"title":"Some Title","length":"123","album":{"id":"6e32ae476815f365"}}
However, the server expects a JSON body like this:
{"title":"Some Title","length":"123","album":"6e32ae476815f365"}
I.e. the relationship album should be a flattened foreign key instead of a nested object. But when I try to alter the albumRelationshipMapping like this:
[albumRelationshipMapping setIdentificationAttributes:#[ #"albumID" ]];
[albumRelationshipMapping addAttributeMappingToKeyOfRepresentationFromAttribute:#"albumID"];
it throws an exception.
(NSInvalidArgumentException', reason: '*** -[NSProxy doesNotRecognizeSelector:allKeys] called!')
Anybody knows what I'm doing wrong here? Or is there example code out there, which can steer me in the right direction?
Sorry if this question was already answered somewhere else. I searched all of stackoverflow and the google groups, but couldn't find a specific solution for my case (RestKit 0.20, CoreData, relationship with just a FK).
Thanks, Dirk
In this case, I think you can simply use the dot notation to get albumID directly (no need to use RKRelationshipMapping)
Try updating your code this way :
// Request Mapping
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
RKEntityMapping *albumRelationshipMapping = [RKEntityMapping mappingForEntityForName:#"Album" inManagedObjectStore:[objMan managedObjectStore]];
//[albumRelationshipMapping addAttributeMappingsFromDictionary:#{#"id": #"albumID", }];
//[requestMapping addAttributeMappingsFromDictionary:#{ #"title": #"title", #"length": #"length" }];
[requestMapping addAttributeMappingsFromDictionary:#{ #"title": #"title", #"length": #"length", #"album" : #"album.albumID" }];
//[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"album"
// toKeyPath:#"album"
// withMapping:albumRelationshipMapping]];