Mapping RestKit Post Response - objective-c

I am posting to a uri using RestKit
NSString* path = [NSString stringWithFormat:#"myPathHere"];
NSDictionary *params = [NSDictionary dictionaryWithKeysAndObjects:#"rating",[rating stringValue], nil];
[[RKObjectManager sharedManager].client post:path params:params delegate:self];
The server will respond with an updated version of my managedObject "Item".
How do I go about mapping this item. Do I use the following delegate method?
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response{}
I know that from here I can get the response JSON value but how do I change that into the mapped object?
Any help is much appreciated.
Thanks!

When I've needed to map my server objects I make the request like this:
[[RKObjectManager sharedManager]
loadObjectsAtResourcePath:[qualifiedResourceURL
appendQueryParams:params]
delegate:self
];
and then the object comes back in this method:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects
I then post the updated object with the Notification Manager
[[NSNotificationCenter defaultCenter] postNotificationName:GotFotosInRegion_Fotos_Array_Response object:objects];
The best documentation is at https://github.com/RestKit/RestKit/blob/master/Docs/Object%20Mapping.md

Related

RESTKit DELETE request not deleting local object on 2xx success

According to the docs for 0.20 RK:
RKManagedObjectRequestOperation adds special behavior to DELETE requests. Upon retrieving a successful (2xx status code) response for a DELETE, the operation will invoke deleteObject: with the operations targetObject on the managed object context. This will delete the target object from the local store in conjunction the successfully deleted remote representation.
I have been trying to delete an object with such a request but no matter what I try I can't seem to get it to work. I successfully perform a request for many objects which get mapped to appropriate class, and get stored in core data. When I attempt a delete request on one of the objects and get a 200 success back, it does not deleted from local store.
Here's some code where I am no doubt missing a trick.
AppDelegate.m
...
//
// Match Mapping
//
RKEntityMapping *matchMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Match class])
inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *matchAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
#"objectId", #"id",
#"score", #"matchScore",
#"date", #"matchDate",
nil];
matchMapping.identificationAttributes = #[#"objectId"];
[matchMapping addAttributeMappingsFromDictionary:matchAttributes];
// Response descriptor for GET
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:matchMapping
method:RKRequestMethodGET
pathPattern:#"match/"
keyPath:#"matches"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// Response Descriptor for PUT
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:matchMapping
method:RKRequestMethodPUT
pathPattern:#"match/"
keyPath:#"match"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// Request Descriptor for DELETE
[objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:[matchMapping inverseMapping]
objectClass:[Match class]
rootKeyPath:nil
method:RKRequestMethodDELETE]];
MatchDetailVC.m
...
- (void)deleteMatch {
NSDictionary *requiredParameters = #{
#"APIKey": #"xxxxx"
};
NSMutableURLRequest *request = [[RKObjectManager sharedManager] requestWithObject:self.match
method:RKRequestMethodDELETE
path:#"match/"
parameters:requiredParameters];
RKManagedObjectRequestOperation *operation = [[RKObjectManager sharedManager]
managedObjectRequestOperationWithRequest:request
managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//[[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext save:nil]; // IS THIS NEEDED?
NSLog(#"Successfully deleted match.");
[self.navigationController popToRootViewControllerAnimated:YES];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error localizedDescription]);
}];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:operation];
}
...
Thanks in advance and if you need more code, let me know.
Andy
I know this is quite an old post, but here is what I found out after searching for ages...
The local delete will fail if there is no valid response mapping for the DELETE response.
The problem wen't away for me when I created an empty response mapping like this:
RKObjectMapping* nullMapping = [RKObjectMapping mappingForClass:[NSNull class]];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:nullMapping method:RKRequestMethodDELETE pathPattern:#"mybase/something/:myid/" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];

sending JSON using simple RKClient post

I'm trying to send a request using RestKit. I want it to be sent in JSON, but it seems that default post methot from RKClient uses some kind of FORM data formatting (I check it using [request HTTPBodyString]).
I need to send the data using simple POST, like this (I'm not using object mapping):
[[RKClient sharedClient] post:path usingBlock:^(RKRequest* req) {
req.params = params;
req.delegate = self;
}];
I found some solutions that use NSJSONSerialization, but that method crashes when I put NSDate object in my JSON dictionary.
Is there a way to tell the RKClient to send requests using JSON?
Update: params is a NSDictionary. I want to be able to tell RestKit to serialize my dictionary using JSON when sending it by POST.
You can do this by setting the RestKit's serializationMIMEType property to RKMIMETypeJSON.
E.g.
[RKObjectManager sharedManager].acceptMIMEType = RKMIMETypeJSON;
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeJSON;
see also http://restkit.org/api/master/Classes/RKObjectManager.html:
The desired serialization format is configured by setting the
serializationMIMEType property. RestKit currently supports
serialization to RKMIMETypeFormURLEncoded and RKMIMETypeJSON.
Update:
Example for posting a plain JSON String with the correct Mime Type:
RKParams *params = [RKRequestSerialization serializationWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]
MIMEType:RKMIMETypeJSON];
[[RKObjectManager sharedManager].client post:#"/mypath"
params:params
delegate:self];
Update 2:
Getting JSON from a NSDictionary object:
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myNSDictionaryObject
options:NSJSONWritingPrettyPrinted
error:&error];
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

How to specify Restkit object manager to perform a POST

I am trying to send a POST request using ios Restkit. I can perform a GET, but cannot find how to send a POST.
My current code looks like the following:
RKURL *URL = [RKURL URLWithBaseURL:[objectManager baseURL] resourcePath:#"/users/sign_in.json" queryParameters:params];
[objectManager loadObjectsAtResourcePath:[NSString stringWithFormat:#"%#?%#", [URL resourcePath], [URL query]] delegate:self];
Apparently, this performs a GET. Any idea what I should add to make it a POST?
Thanks!
You can configure your RKRequest with:
[request setMethod:RKRequestMethodPOST];
To answer your question (regardless of the above discussion) you can do the following:
[objectManager loadObjectsAtResourcePath: #"path" usingBlock: ^(RKObjectLoader *loader) {
loader.HTTPMethod = RKRequestMethodPOST;
}];

Get JSON with HTTP Authentication with AFNetworking

I am trying to get a JSON from my hudson url address and authenticate my (Mac OS X) application using the HTTP Authenticate.
Following the example I'm using:
// AppDelegate.m
- (void) doSomething {
[[CommAPIClient sharedClient] getPath:#"/computer/api/json" parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Name: %#", [responseObject valueForKeyPath:#"totalExecutors"]);
} failure:nil];
}
// CommAPIClient.m
+ (CommAPIClient *) sharedClient {
static CommAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString: [appDelegate.hudsonTextField stringValue]]];
});
return _sharedClient;
}
- (id) initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (self){
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
userName = [appDelegate.userTextField stringValue];
password = [appDelegate.passwdTextField stringValue];
[self setAuthorizationHeaderWithUsername:userName password:password];
[self setDefaultHeader:#"Accept" value:#"application/json"];
}
return self;
}
I want to get the computer's list to show in my dropdown, but this two lines does not work together:
[self setAuthorizationHeaderWithUsername:userName password:password];
[self setDefaultHeader:#"Accept" value:#"application/json"];
If I just use the first line, my authetication works, but I receive that error because I try to get a key:
2012-02-03 02:43:57.542 HudsonSlave[7523:707] An uncaught exception was raised
2012-02-03 02:43:57.542 HudsonSlave[7523:707] [<NSConcreteData 0x100850000> valueForUndefinedKey:]: this class is not key value coding-compliant for the key totalExecutors.
2012-02-03 02:43:57.623 HudsonSlave[7523:707] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSConcreteData 0x100850000> valueForUndefinedKey:]: this class is not key value coding-compliant for the key totalExecutors.'
If use the second line, my authentication will return an error 403.
Anyone could help with problem ?
Thanks and apologize for any errors in english.
Thiago
I ran into this issue with the Flickr API and ended up subclassing AFJSONRequestOperation which turned out to be trivial. You need only override the class method defaultAcceptableContentTypes thusly, for example:
+ (NSSet *)defaultAcceptableContentTypes;
{
return [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", #"text/plain", nil];
}
Then in my AFHTTPClient subclass, I just register my subclass as the default HTTP operation class:
[self registerHTTPOperationClass:[CCFFlickrJSONRequestOperation class]];
UPDATE 2013-09-09 14-52-47:
The method name is now + (NSSet *)acceptableContentTypes
"Expected content type {( "text/javascript", "application/json", "text/json" )}, got application/javascript"
Just like the error said, the request was expecting one of text/javascript, application/json, or text/json, but the server sent application/javascript (which is an incorrect mime type for JSON).
Either get your server to respond with the correct mime type, or (perhaps easier) manually change the line in AFJSONRequestOperation that specifies which mime types are accessible. There's a less hacky alternative to changing the library code that involves subclassing, but that's probably much more trouble than it's worth in your case.
Actually you can now add more content-types easily:
[AFHTTPRequestOperation addAcceptableContentTypes:[NSSet setWithObjects:#"text/html", nil]];
If it's a one-time thing instead of subclassing the whole AFJSONRequestOperation object you can just set the acceptable content types directly to whatever you need:
AFJSONRequestOperation *operation = blah blah blah
[operation setAcceptableContentTypes:[NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", #"text/plain",#"text/html", nil]];
[operation start];
As of AFNetworking 2.0, serialization is no longer a part of RequestOperation classes, and is delegated to Serializer classes. If you use 2.0, you want to do something like this (in general I think it's the best solution to extend acceptableContentTypes instead of replacing it - you never know what base values it will have in future AFNetworking SDK versions):
AFHTTPRequestOperationManager *requestManager = [AFHTTPRequestOperationManager manager];
NSMutableSet *contentTypes = [requestManager.responseSerializer.acceptableContentTypes mutableCopy];
[contentTypes addObject:#"text/html"];
[contentTypes addObject:#"text/plain"];
// ... etc ...
requestManager.responseSerializer.acceptableContentTypes = [contentTypes copy];
If you decide to use an AFHTTPRequestOperation object directly, just replace "requestManager.responseSerializer" with "myOperationObject.responseSerializer".

Send post request and map response to object

I am new to restKit and I have a few questions for you. I
cant understand how to send Post request using json/xml to my web
services and map the incoming reply with my classes. Can any one give
me a help on that. The code I am using is this:
in my applicationDelegate I am instantiating the RKObjectManager
providing the base URL:
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"https://example.com/services/"];
// Request params in Dictionary
NSArray *objects = [NSArray arrayWithObjects: email, password,
nil];
NSArray *keys = [NSArray arrayWithObjects:#"username",
#"password", nil];
NSDictionary *params = [NSDictionary dictionaryWithObjects:objects
forKeys:keys];
NSLog(#"Manager: %#", [RKObjectManager
sharedManager].description);
// User object Mapping
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:
[User class]];
[userMapping mapKeyPath:#"userName" toAttribute:#"userName"];
[userMapping mapKeyPath:#"lastName" toAttribute:#"lastName"];
[userMapping mapKeyPath:#"active" toAttribute:#"active"];
[[RKObjectManager sharedManager].mappingProvider
setMapping:userMapping forKeyPath:#"user"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/
login" delegate:self];
When a post is send to /login the server should send back a valid json
and then map that json to my User class.
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:
(NSArray*)objects {
RKLogInfo(#"Load collection of Articles: %#", objects);
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:
(NSError *)error
{
NSLog(#"Objecy Loader failed: %#", error);
}
- (void)request:(RKRequest *)request didFailLoadWithError:(NSError
*)error
{
NSLog(#"Request failed");
}
- (void)request:(RKRequest*)request didLoadResponse:
(RKResponse*)response {
if ([request isGET]) {
// Handling GET /foo.xml
if ([response isOK]) {
// Success! Let's take a look at the data
NSLog(#"Retrieved XML: %#", [response bodyAsString]);
}
} else if ([request isPOST]) {
// Handling POST /other.json
if ([response isXML]) {
///NSLog(#"Seng a JSON request:! \n %#", [request
HTTPBodyString]);
NSLog(#"Got a responce! \n %#", [response bodyAsString]);
}
} else if ([request isDELETE]) {
// Handling DELETE /missing_resource.txt
if ([response isNotFound]) {
NSLog(#"The resource path '%#' was not found.", [request
resourcePath]);
}
}
}
When I execute it the objectLoader method are not triggered, my
understanding of restkit is that they should get called when
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/login"
delegate:self];
is executed ? Any help is appreciated :)
Well, you do not even send your data to the server. A couple of days ago, I wrote a snippet explaining how to post a NSDictionary (as JSON) to a server using RestKit.
did you remember to set the delegate for restkit to that user class (or where ever you have the delegates being caught) with
#interface User : NSObject<RKObjectLoaderDelegate>{
}
you probably did but its worth mentioning incase :P
and the self that gets passed in this line is that class?
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/
login" delegate:self];
also i think the newer way to add the mapping is and set up for posts is
[sharedManager.mappingProvider addObjectMapping:userMapping];
[sharedManager.mappingProvider setMapping:userMapping forKeyPath:#"/somepath"];
[sharedManager.mappingProvider setSerializationMapping:[userMapping inverseMapping] forClass:[User class]];
// Must set the router up to handle posts
[sharedManager.router routeClass:[User class] toResourcePath:#"/api/users" forMethod:RKRequestMethodPOST];
edit again : and create a post using the loader like this. probably all over kill for what your doing but cant hurt to have a look
RKObjectLoader* loader = [RKObjectLoader loaderWithResourcePath:url objectManager:self.objectManager delegate:self.currentDelegate];
loader.method = RKRequestMethodPOST;
loader.sourceObject = self;
loader.targetObject = self;
loader.serializationMIMEType = self.objectManager.serializationMIMEType;
loader.serializationMapping = [self.objectManager.mappingProvider serializationMappingForClass:self.mappingClass];
loader.objectMapping = [self.objectManager.mappingProvider objectMappingForClass:self.mappingClass];
[loader send]; //<<the actual post