My JSON response looks like this:
{
"enabled": false,
"number_of_articles": 20,
"new_users": [
{
"id": "5001",
"name": "Jimmy Valner"
},
],
"articles" : [
{
"id" : 33122,
"title" : "Something",
"authors": [
{
"id": "511",
"name": "Somebody"
},
{
"id": "51",
"name": "Somebody else"
}
}]
}
Not really a proper RESTful response I know. But I have to map it somehow to two different Entities (User and Article). A already have a working code for the articles part. It maps the Article and the Articles as a relation.
I'm doing it like this:
RKMapping *mapping = [MappingProvider articlesMapping];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:mapping
pathPattern:#"/articles/:userId/latest"
keyPath:#"articles"
statusCodes:statusCodeSet];
NSURLRequest *request = [[RKObjectManager sharedManager] requestWithObject:nil
method:RKRequestMethodGET
path:[NSString stringWithFormat:#"/articles/%#/latest", [[CredentialStore sharedInstance] userId]]
parameters:nil];
RKManagedObjectStore *store = [[DataModel sharedDataModel] objectStore];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request
responseDescriptors:#[responseDescriptor]];
operation.managedObjectCache = store.managedObjectCache;
operation.managedObjectContext = store.mainQueueManagedObjectContext;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"%#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#", error);
NSLog(#"Response: %#", operation.HTTPRequestOperation.responseString);
}];
[operation start];
This is working as expected. I provide the mapping instructions in the + (RKMapping *)articlesMapping method. Even the authors part of the JSON response is mapped properly.
Now I want to map new_users array to a different entity (User obviously). How can I do that by not invoking another request to the server ? I had some success with creating another RKManagedObjectRequestOperation *operation but that of course triggers another network request to the server which I want to avoid.
Should I look for the solution here in the RKObjectManager or should I somehow change the keyPath to start at root level and the do the mapping for different entities in the + (RKMapping *) method (probably not) ?
And another question .. how can I access the enabled, number_of_articles properties from this JSON? Again, I'm not dealing with a nice structured Restful response, but I have to deal with it somehow. I only want to access this properties somehow ... no mapping to entities needed.
RKManagedObjectRequestOperation has the method initWithRequest:responseDescriptors: for a reason. Note specifically the 's' on responseDescriptors and the fact that you're passing an array. This means you can configure multiple response descriptors, each with the same path pattern but different key paths and RestKit will process each of them on the response received from the server.
Note that when you do this you probably want to change your NSLog(#"%#", mappingResult.array); because it would contain all of the top level items in the list. Instead, you should probably use NSLog(#"%#", mappingResult.dictionary);
Related
I am using Restkit for a class that Extends NSManagedObject.
I am aware that rest kit itself has functionality to save to core data from network fetch. However, I cannot use that functionality due to the following reasons:
My application will be fetching data from sockets as well as from rest kit , so I would want a centralised location for saving/deleting logic.
My server does not confirm to rest protocols, so many times I have to send a POST request even when I really want to delete something in server.
So What I wanted to do was have my Model classes extend nsmanaged object, and save it when I want to. But I get this error:
CoreData: error: Failed to call designated initializer on
NSManagedObject class
Is there a way to go around this ?
I am fetching from server like this :
#implementation API_Login
+(void)performLoginWithEmail:(NSString*)email
withPassword:(NSString*)password
success:(void (^)(Token* user) )success
failure:failureblock failure{
RKObjectManager * objectManager = [APIHelper getRestObjectManager];
RKObjectMapping *tokenMapping = [RKObjectMapping mappingForClass:[Token class]];
//add mapping for token
[tokenMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"token"]];
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:tokenMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
// add mapping for error
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
[errorMapping addAttributeMappingsFromDictionary:#{#"message":#"message",#"badRequest":#"badRequest"}];
RKResponseDescriptor *errorResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:errorMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorResponseDescriptor];
NSDictionary *queryParams = #{#"email" : email,
#"password" : password,
};
[objectManager postObject:nil
path:#"/users/api/login"
parameters:queryParams
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
Token* token = (Token*)[mappingResult firstObject] ;
// [AppDelegateHandle setToken:token];
success(token);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSArray* e = [[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey];
Error *err = (Error*)[e objectAtIndex:0];
NSLog(#"%#",[err.badRequest allValues] );
failure(operation,error);
}];
}
#end
My Token class looks like:
#interface Token : NSManagedObject
#property NSString* token;
#end
and my api response looks like :
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6XC9cL3JlbWluZGVyLmRldlwvdXNlcnNcL2FwaVwvbG9naW4iLCJpYXQiOjE0Njg1OTM3NjYsImV4cCI6MTQ2OTE5Mzc2NiwibmJmIjoxNDY4NTkzNzY2LCJqdGkiOiIxMDc3ZjBhY2ViYTFjOWZjZWNhYjkyMzYyOTA0ZmI4NSJ9.I6FHJLCCHr3EHQa8HgaDqxQMjF1HVyA5AymPjvBGDrM"
}
When I change Token to extend NSObject instead of NSManagedObject , everything works fine. What could the problem be ?
This happens because you're using RKObjectMapping instead of RKEntityMapping which is required if you're using a subclass of NSManagedObject.
You can't use a subclass of NSManagedObject if you aren't going to add it directly into a context.
If your request simply has a token then I wouldn't bother with RestKit probably, but in the general case I'd map to NSDictionary with the keys being the same as your managed object classes and then when you want to create your managed objects you can do so and 'import' the data to them with setValuesForKeysWithDictionary:.
how to i set response mapping to manager with path pattern ..if the getobjects at path is different from path pattern that is used to map the response.
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:A
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
[manager getObjectsAtPath:A/ID
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#" Category success");
[self.delegate didReceiveAssignedCategories];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Category failure");
}];
response mapping path ie:A must be set to dynamic path used to getobject ie:A/ID .
ex:
Call 1)
A = /getAllCategories
A/ID = /getAllCategories/123
call 2)
A = /getAllCategories
A/ID = /getAllCategories/456
response mapping is same for 123, 456
only while getting the objects i am using different urls ie: with id's attached.
how to do that ?
If you have 2 path patters which both return the same type of data then you can use the same mapping with 2 different response descriptors.
If you have 1 path pattern which can return 2 different types of data then you need to use RKDynamicMapping to 'intercept' the incoming data and decide which mapping is actually required.
From your edited question, 'pattern' is the important thing that you have misunderstood. You need to use a path pattern, not a static path:
#"getAllCategories/:identity"
1) First create response mapping like
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:#"getAllCategories/:categoryID"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
2)Create Class with categoryID in it.
[CategoryRequest class]
3) create object of that class and set categoryID
CategoryRequest *categoryRequest = [CategoryRequest alloc] init];
categoryRequest.categoryID = #"123";
4)call getobject using that object
[manager getObject:categoryRequest
path:#"getAllCategories/123"
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
if another call is required to be made for same mapping create object of of category request class set new category id and call get object using that categoryresquest and required path patter.
I have a request gives me two part of data, the json looks like this
{
"banner_content":
[
{
"activi_id":"1",
"activi_pic":
},
{
"activi_id":"2",
"activi_pic":
},
],
"categories":
[
{...},
{...},
]
}
When I write responseDescriptor to map the data, I have found I must define a model which contains banner_content array and categories array (I don't want to )
or else I have to write two responseDescriptors to do, when the request is done, I have to get the two parts of data from (RKMappingResult *)mappingResult
then get array like [mappingResult objectForKey:#"banner_content"] and [mappingResult objectForKey:#"categories"]
it's weird
below is my code
File:RCategory.m
#implementation RCategory
+ (NSDictionary *)_mapping {
return #{#"title" : #"title"};
}
+ (RKObjectMapping *)mapping {
// Setup our object mappings
RKObjectMapping *categoryMapping = [RKObjectMapping mappingForClass:[self class]];
[categoryMapping addAttributeMappingsFromDictionary:[[self class] _mapping]];
RKObjectMapping *itemMapping = [RCategoryItem mapping];
RKRelationshipMapping* relationShipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:#"content"
toKeyPath:#"items"
withMapping:itemMapping];
[categoryMapping addPropertyMapping:relationShipMapping];
return categoryMapping;
}
#end
File:RAd.m ignored
File:viewController.m
- (void)loadCategory {
// Load the object model via RestKit
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKObjectMapping *categoryMapping = [RCategory mapping];
RKResponseDescriptor *categoryResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"category"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKObjectMapping *adsMapping = [RAd mapping];
RKResponseDescriptor *adResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:adsMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"banner_content"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
//** DO I MUST ADD TWO RESPONSE_DESCRIPTOR FOR ONE REQUEST??**
[objectManager addResponseDescriptor:categoryResponseDescriptor];
[objectManager addResponseDescriptor:adResponseDescriptor];
[objectManager getObjectsAtPath:RPATH(CATEGORY_PATH)
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSArray* statuses = [mappingResult array];
[self.categories addObjectsFromArray:statuses];
if ([self isViewLoaded]) {
[self.tableView reloadData];
}
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
Does anybody have a better way to solve my problem?
BTW I write -mapping in the Model Class, It's the easiest and best way I can think out
I would like to know how do you write the mapping.
You need 2 response descriptors because the response has 2 logically separate parts. This is fine. If you were using Core Data then you wouldn't be concerned as all your objects would simply be saved to the context and you can fetch them as required. With object mapping, if you don't care about the grouping then you can get an array of all objects from the mapping result.
Creating your mappings from data returned by the model objects is fine - but it does limit you because you can only have one source key per destination key. What happens when you have a different response for the same object with a different key that means the same as some other key (hopefully you don't, but it all depends on the server API).
I'm having an issue with ResKit for managing orphaned objects when, the JSON received from the same webService, must be mapped in multiple entities.
I've followed the example here http://restkit.org/api/latest/Classes/RKManagedObjectRequestOperation.html (under section "Deleting orphaned objects") and I can get orphaned objects deleted, but only for a single entity.
Let's see some code...
What follow is a (small part) JSON that I receive from webService.
{
"families": [
{
"id": "000",
"desc": "desc_1"
},
{
"id": "001",
"desc": "desc_2"
}
],
"categories": [
{
"id": "00000000",
"desc": "test"
....//others attributes
},
{
"id": "00000001",
"desc": "test"
....//others attributes
}
]
}
I'm mapping this JSON correctly to 2 different entities: Category and Family.
Now, for what I've understand, I can delete orphaned objects (objects no more received from server) by adding a FetchRequestBlock to my RKObjectManager.
Here is how I define it:
[objectManager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {
RKPathMatcher* pathMatcher = [RKPathMatcher pathMatcherWithPattern:#"getData.asp"];
NSDictionary *dic = nil;
if ([pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:YES parsedArguments:&dic]) {
NSFetchRequest *fetchRequest = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Family" inManagedObjectContext: managedObjectStore.mainQueueManagedObjectContext];
fetchRequest.entity = entity;
return fetchRequest;
} else
return nil;
}];
My questions: as you can see, in the FetchRequestBlock, I'm defining only the entity "Family", but I want to delete orphaned objects of "Category" entity inside the same block...is this possible ? Or I have to create another similar block that differs only for the entity name ?
And is there a method to add a "keyPath" (like "categories" or "families" in this example) to the FetchRequestBlock so I can be sure to delete the correct NSManagedObject? Or is this unnecessary?
EDIT:
For completion I add some more code, so I think you can understand better what I'm doing (and, in case, correct me if I'm doing something wrong...)
Here is how I define RKResponseDescriptor, where I can correctly associate it to a "KeyPath":
//Categories
RKResponseDescriptor *categoriesDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:categoriesMapping
method:RKRequestMethodGET
pathPattern:#"getData.asp" keyPath:#"categories"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
//Families
RKResponseDescriptor *familiesDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:familiesMapping
method:RKRequestMethodGET
pathPattern:#"getData.asp" keyPath:#"families"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
And I retrieve data from webService using the following code:
[[RKObjectManager sharedManager] getObjectsAtPath:#"getData.asp" parameters:#{kAuthKeyName : kAuthKeyValue} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//Success block
//Before entering in this block, I can see with debug that ResKit try to
//automatically delete objects that return from the "FetchRequestBlock" defined in above code...
}failure:^(RKObjectRequestOperation *operation, NSError *error) {
//Failure Block
}];
how to i set response mapping to manager with path pattern ..if the getobjects at path is different from path pattern that is used to map the response.
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:A
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
[manager getObjectsAtPath:A/ID
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#" Category success");
[self.delegate didReceiveAssignedCategories];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Category failure");
}];
response mapping path ie:A must be set to dynamic path used to getobject ie:A/ID .
ex:
Call 1)
A = /getAllCategories
A/ID = /getAllCategories/123
call 2)
A = /getAllCategories
A/ID = /getAllCategories/456
response mapping is same for 123, 456
only while getting the objects i am using different urls ie: with id's attached.
how to do that ?
If you have 2 path patters which both return the same type of data then you can use the same mapping with 2 different response descriptors.
If you have 1 path pattern which can return 2 different types of data then you need to use RKDynamicMapping to 'intercept' the incoming data and decide which mapping is actually required.
From your edited question, 'pattern' is the important thing that you have misunderstood. You need to use a path pattern, not a static path:
#"getAllCategories/:identity"
1) First create response mapping like
[manager addResponseDescriptorsFromArray:
#[[RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:#"getAllCategories/:categoryID"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]]];
2)Create Class with categoryID in it.
[CategoryRequest class]
3) create object of that class and set categoryID
CategoryRequest *categoryRequest = [CategoryRequest alloc] init];
categoryRequest.categoryID = #"123";
4)call getobject using that object
[manager getObject:categoryRequest
path:#"getAllCategories/123"
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
if another call is required to be made for same mapping create object of of category request class set new category id and call get object using that categoryresquest and required path patter.