Restkit failed to post object - objective-c

I have been using Restkit for GET successfully and now I tried to use it for POST and get error. Here is the code for my RESTKit which I think is quite standard:
RKObjectMapping *mappingTransaction = [RKObjectMapping mappingForClass:[Transaction class]];
NSDictionary *mappingTransactionDict = #{
#"final_price" : #"finalPrice",
#"discount" : #"discount",
#"customer_fk" : #"customerID",
#"typeofpayment_fk" : #"typeOfPaymentID",
#"shop_fk" : #"shopID",
#"systemuser_fk" : #"systemUserID",
#"company_fk" : #"companyID",
#"created_at" : #"creationDate",
#"updated_at" : #"updateDate"
};
[mappingTransaction addAttributeMappingsFromDictionary:mappingTransactionDict];
RKResponseDescriptor *transactionDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:mappingTransaction
method:RKRequestMethodPOST
pathPattern:#"/revenue/add/transaction"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:transactionDescriptor];
[[RKObjectManager sharedManager] postObject:(Transaction *)transactionRecord
path:#"/revenue/add/transaction"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
//self.typeOfPaymentArray = mappingResult.array;
}
failure:^(RKObjectRequestOperation *operation, NSError *error){
NSLog(#"Transaction insert failed': %#", error);
}];
Then there's an error of "TokenMismatchException":
I googled it but seems all threads are talking about CSRF token from a form, which in my case is a third party IOS app. My RESTFul API is run by Laravel 5. Any idea will be appreciated. Thanks for your attention.

In your case, you want to map an object (Transaction) in the request (not in the response), so instead of using responseDescriptorWithMapping you should use this method:
+ (instancetype)requestDescriptorWithMapping:(RKMapping *)mapping objectClass:(Class)objectClass rootKeyPath:(NSString *)rootKeyPath method:(RKRequestMethod)method

Related

How to post without post data using RestKit

I was looking for a way to map an empty object to be posted to an endpoint. The call needs to be a POST, but there shouldn't be any data posted to the endpoint (empty body), it's only about calling the endpoint directly without data.
I've tried doing the same trick as in RestKit: How to handle empty response.body? but using RKRequestDescriptor instead.
Doing so leads to following error when using postData:nil in RKObjectMapping's postObject method:
Uncaught exception: RKRequestDescriptor objects must be initialized with a mapping whose target class is NSMutableDictionary, got 'NSNull' (see [RKObjectMapping requestMapping]);
Using NSNull for the RKRequestDescriptor's mapping seems to work, but nil seems to fail the mapping action.
As the error mentions, it's looking for an NSMutableDictionary for the mapping action. so using an empty NSMutableDictionary like #{} instead of nil for postObject does the trick.
AFRKHTTPClient *client = [self getClient];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
RKObjectMapping *requestMapping = [RKObjectMapping mappingForClass:[NSNull class]];
[objectManager addRequestDescriptor:
[RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:[NSNull class]
rootKeyPath:nil
method:RKRequestMethodAny]];
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[NSNull class]];
[objectManager addResponseDescriptor:
[RKResponseDescriptor responseDescriptorWithMapping:responseMapping
method:RKRequestMethodPOST
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
[objectManager postObject:#{} // <-- this works, but nil doesn't
path:#"/api/some/endpoint"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// succes code here
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// failure code here
}];

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)]];

AFNetworking 2.0 POST Issue

I am in the process of switching over some of my code from AFNetworking 1.0 to 2.0.
Before when doing a POST, I was creating an AFHTTPClient, and an AFHTTPRequestOperation like so:
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:reqUrl];
[httpClient setParameterEncoding:AFJSONParameterEncoding];
httpClient.operationQueue.maxConcurrentOperationCount = 1;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
req.viewName, #"viewName",
req.json, #"JSON",
req.dateAdded.description, #"dateTime",
req.latitude, #"latitude",
req.longitude, #"longitude",
req.heading, #"heading",
req.user, #"requestUser",
nil];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[httpClient registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[op setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation,
id responseObject) {
.......convert responseObject (string) to NSDictionary.....
});
This worked fine, and my POSTs went through and I received a successful text response from the server. (which I then converted to a NSDictionary)
I now am using an AFHTTPSessionManager singleton, and calling the POST method from that. When initializing my AFHTTPSessionManager, I am doing the following:
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
[self setResponseSerializer:responseSerializer];
self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"text/html", nil];
Then in my other class, I am calling the POST like so:
NSDictionary *params = #{
#"viewName":req.viewName,
#"JSON":req.json,
#"dateTime":req.dateAdded.description,
#"latitude":req.latitude,
#"longitude":req.longitude,
#"heading":req.heading,
#"requestUser":req.user
};
[netManager POST:path parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
.....
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//failing here
});
My data has not changed at all, but the POSTs always fail with the error:
Error Domain=AFNetworkingErrorDomain Code=-1011 "Request failed: bad request (400)" UserInfo=0x1704675c0 {AFNetworkingOperationFailingURLResponseErrorKey=<NSHTTPURLResponse: 0x178234660> { URL: ... } { status code: 400, headers {
"Content-Length" = 2738;
"Content-Type" = "text/html";
Date = "Thu, 15 May 2014 16:13:51 GMT";
Server = "Microsoft-IIS/7.0";
"X-Powered-By" = "ASP.NET";
Whats different that is causing the new AFNetworking 2.0 POST code to not work with this now? Is there anything I need to be setting? The URL and Parameters I am passing are the same as they were with the old way I was sending the POST.
Thanks
My solution ended up being a pretty simple one
In my AFHTTPSessionManager's init, I was not setting the RequestSerializer along with the ResponseSerializer.
After setting it correctly, my POSTs are going through fine again. Heres what I set:
[self setResponseSerializer:[AFJSONResponseSerializer serializer]];
self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", nil];
[self setRequestSerializer:[AFJSONRequestSerializer serializer]];
EDIT
Aaron Brager stated that those first 2 lines are defaults and not needed. All I needed was to set the RequestSerializer. I tested and can verify this.

RestKit using cached or old response descriptor

On my main controller, the RESTKIT is working fine:
My code and response descriptor looks like this:
// register mappings with the provider using a response descriptor
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:workOrderMapping
method:RKRequestMethodGET
pathPattern:#"/api/workorder/GetWorkOrderListSimple"
keyPath:nil
statusCodes:nil];
[objectManager addResponseDescriptor:responseDescriptor];
[[RKObjectManager sharedManager] getObjectsAtPath:#"/api/workorder/GetWorkOrderListSimple"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"It Worked");
_workOrders = mappingResult.array;
[self.tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"error': %#", error);
}];
So this first call works fine, however, on my 2nd controller, it seems to be somehow reusing this old response descriptor, I created a new one, but in the error message it's still referencing GetWorkOrderListSimple, when I clearly told it to use GetWorkOrderDetail.
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:workOrderBigMapping
method:RKRequestMethodGET
pathPattern:#"/api/workorder/GetWorkOrderDetail"
keyPath:nil
statusCodes:nil];
However for some reason, here is my error message, can anyone point me in the right direction for debugging? Thanks!!!
A 200 response was loaded from the URL 'http://xxxxxxx.ws/api/workorder/GetWorkOrderDetail?workOrderId=116194', which failed to match all (1) response descriptors:
http://xxxxxxx.ws pathPattern=/api/workorder/GetWorkOrderListSimple statusCodes=(null)> failed to match: response path '/api/workorder/GetWorkOrderDetail?workOrderId=116194' did not match the path pattern '/api/workorder/GetWorkOrderListSimple'.
I have the same "loading" or "setup" code in the Viewdidload of each view controller, there are two view controllers
I call configureRestKit in every Viewdidload, should I not? Should this be in the app delegate or somewhere else?
I thought since I was configuring the kit in each view controller viewdidload it would be a fresh one every time
- (void)configureRestKit
{
// initialize AFNetworking HTTPClient
NSURL *baseURL = [NSURL URLWithString:#"http://xxxxxxxx.ws"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
// initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
// setup object mappings
RKObjectMapping *workOrderBigMapping = [RKObjectMapping mappingForClass:[WorkOrderBig class]];
[workOrderBigMapping addAttributeMappingsFromArray:#[#"WorkOrderId", #"Job", #"Address", #"Supervisor", #"PO", #"Priority", #"Status", #"ReceivedDate"]];
RKObjectMapping *workOrderDetailMapping = [RKObjectMapping mappingForClass:[WorkOrderDetail class]];
[workOrderDetailMapping addAttributeMappingsFromArray:#[#"WorkOrderDetailId", #"WorkOrderId", #"WorkOrderProblemId", #"DetailDescription", #"ProductId", #"Qty", #"PONumber", #"Code", #"ProductDescription", #"UOM", #"Price", #"OriginalPrice", #"PctMarkup", #"LineItem", #"OriginalTotal", #"TotalPrice"]];
RKObjectMapping *workOrderProblemMapping = [RKObjectMapping mappingForClass:[WorkOrderProblem class]];
[workOrderProblemMapping addAttributeMappingsFromArray:#[#"WorkOrderId", #"WorkOrderProblemId", #"Description", #"SpanishDescription", #"Action", #"LineItem"]];
//Define Relationships
[workOrderBigMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Details"
toKeyPath:#"Details"
withMapping:workOrderBigMapping]];
[workOrderBigMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Problems"
toKeyPath:#"Problems"
withMapping:workOrderProblemMapping]];
// register mappings with the provider using a response descriptor
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:workOrderBigMapping
method:RKRequestMethodGET
pathPattern:#"/api/workorder/GetWorkOrderDetail"
keyPath:#"/api/workorder/GetWorkOrderDetail"
statusCodes:nil];
[objectManager addResponseDescriptor:responseDescriptor];
- (void)loadWorkOrders
{
NSString *WorkOrderId = [NSString stringWithFormat:#"%i", _workOrderId];
NSMutableDictionary *params =[[NSMutableDictionary alloc] init];
[params setValue:WorkOrderId forKey:#"workOrderId"];
[[RKObjectManager sharedManager] getObjectsAtPath:#"/api/workorder/GetWorkOrderDetail"
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"It Worked");
_workOrders = mappingResult.array;
//paint screen
WorkOrderBig *mainWorkOrder = [_workOrders objectAtIndex:0];
self.lblWorkOrderId.text = mainWorkOrder.WorkOrderId;
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"What do you mean by 'there is no coffee?': %#", error);
}];
}
I call configureRestKit in every Viewdidload, should I not?
Based on your configuration, yes, you should
Should this be in the app delegate or somewhere else?
No, the app delegate is for application level event management, not data configuration
So, your configuration is almost fine, the problem is your usage: [RKObjectManager sharedManager]. When you call sharedManager you will always get back the first object manager that was created - not the one that was 'locally' created.
You should be using an #property on each of your view controllers to store the objectManager that they create and then using self.objectManager instead of [RKObjectManager sharedManager].

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