RestKit - Trying to post object to server - objective-c

This has probably been asked a million times, but I'm not getting anywhere from all the examples that I'm finding.
I have the following class:
#interface LoginRequest : NSObject
{
NSString* _deviceId;
}
#property (nonatomic, retain) NSString* deviceId;
#end
And I'm trying to post this to a server, encoded as form key/value in the HTTP body.
I'm using the following to setup the mapping:
objectManager.serializationMIMEType = RKMIMETypeFormURLEncoded;
RKObjectMapping* pmsg = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
[pmsg mapKeyPath: #"deviceId" toAttribute:#"DeviceId"];
RKObjectMapping* pmsgSerializeMapping = [pmsg inverseMapping];
[objectManager.mappingProvider setSerializationMapping:pmsgSerializeMapping forClass:[LoginRequest class]];
[objectManager.router routeClass:[LoginRequest class] toResourcePath:#"/login" forMethod:RKRequestMethodPOST];
This was taken from the example here: https://github.com/RestKit/RestKit/wiki/Posting-Data-Objects
I'm getting the following error:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<LoginRequest 0x8c9c980> valueForUndefinedKey:]: this class is not key value coding-compliant for the key DeviceId.'
Here's how I'm trying to send my object:
RKObjectManager *objectManager = [RKObjectManager sharedManager];
LoginRequest* request = [LoginRequest alloc];
request.deviceId = #"Test";
[objectManager postObject:request delegate: self];
Can anyone help?
Update: I can confirm that when I don't use the custom key-code mapping stuff, it works. The following I have working:
RKObjectMapping* pmsg = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
[pmsg mapAttributes:#"DeviceId", nil];
[objectManager.mappingProvider setSerializationMapping:pmsg forClass:[LoginRequest class]];
[objectManager.router routeClass:[LoginRequest class] toResourcePath:#"/login" forMethod:RKRequestMethodPOST];

Well, your property is named "deviceId", which is lowercase. But you're saying the attribute is "DeviceId" which starts with an uppercase "D".

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

Mapping to Multiple Classes in Restkit

I am using RestKit to map data to properties in two classes, Region and Category, for use in two table views, Regions and Categories. However, it would appear that it is only correctly mapping data for the first view that I go to when testing the application. For instance, when I view Categories first, then switch the Regions, my regions are getting stored as objects of type Category. If I view Regions first, then Categories, my categories get mapped as objects of type Region. I can tell that the right information is being retrieved, due the number of objects, but it is not being stored as the correct type. How can I make sure that each mapping request is done correctly? The code for each mapping is below:
Categories:
RKURL *baseURL = [RKURL URLWithBaseURLString:#"MyUrl"];
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;
RKObjectMapping *categoryMapping = [RKObjectMapping mappingForClass:[Category class]];
[categoryMapping mapKeyPathsToAttributes:#"categoryID", #"categoryID", #"parentID", #"parentID", #"categoryName", #"categoryName", #"childrenCount", #"childrenCount", #"parentCount", #"parentCount", #"catCount", #"catCount", nil];
[objectManager.mappingProvider setMapping:categoryMapping forKeyPath:#""];
Regions:
RKURL *baseURL = [RKURL URLWithBaseURLString:#"MyUrl"];
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;
NSLog(#"URL Created");
RKObjectMapping *regionMapping = [RKObjectMapping mappingForClass:[Region class]];
[regionMapping mapKeyPathsToAttributes:#"regionHome", #"regionHome", #"regionID", #"regionID", #"regionName", #"regionName", #"parentCount", #"parentCount", #"parentID", #"parentID", #"childrenCount", #"childrenCount", #"parentName", #"parentName", nil];
[objectManager.mappingProvider setMapping:regionMapping forKeyPath:#""];
In my judgment you don't want to leave the key path as #"" in [objectManager.mappingProvider setMapping:regionMapping forKeyPath:#""];. You will probably replace those with #"region" and #"category" so RestKit can tell based on the key path what objects it should expect.
Also, are you using the isKindOfClass: method to distinguish the objects in the objectLoader didLoadObjects method?
Perhaps this will help you:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
if ([[objects objectAtIndex:0] isKindOfClass:[Region class]]) {
Region *region = [objects objectAtIndex:0];
}
else if ([[objects objectAtIndex:0] isKindOfClass:[Category class]]) {
Category *category = [objects objectAtIndex:0];
}
}

iPhone Core Data "Failed to call designated initializer on NSManagedObject class" error while deserializing JSON into a nested core data entity

I'm using RestKit to convert a nested core data model into a JSON file and upload it to a web service. Then I'm trying to get request the same JSON back and re-inflate it into a core data object. I'm getting this error during inflation:
CoreData: error: Failed to call designated initializer on NSManagedObject class 'AppUser'
2012-04-26 10:25:50.850 DropboxSync[3824:5843] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AppUser 0x2b7820> valueForUndefinedKey:]: the entity (null) is not key value coding-compliant for the key "lastName".'
The class has a lastName property:
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * lastName;
#property (nonatomic, retain) NSString * localDataFilepath;
#property (nonatomic, retain) NSSet *events;
#property (nonatomic, retain) AppUserWrapper *wrapper;
**What could be causing this?**
I got 3 entities defined in my Core Data model:
User
Event
Images
user has multiple events, events have multiple images
I've defined a RestKit object mapping as defined below.
-(void)setupObjectMapping
{
RKObjectManager *objectManager = [RKObjectManager sharedManager ] ;
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Article class]];
[mapping mapAttributes:#"articleID", #"title", #"body", nil];
//********************************
RKObjectMapping *imageMapping = [RKObjectMapping mappingForClass:[ImageEntity class]];
[imageMapping mapAttributes:#"createDate", #"localFilePath", nil];
[objectManager.mappingProvider addObjectMapping:bleedImageMapping];
[objectManager.mappingProvider setSerializationMapping:[imageMapping inverseMapping] forClass:[ImageEntity class]];
[objectManager.mappingProvider imageMapping forKeyPath:#"images"];
//********************************
RKObjectMapping *eventMapping = [RKObjectMapping mappingForClass:[Event class]];
[eventMapping mapAttributes:#"createDate", #"severity", nil];
[eventMapping mapRelationship:#"images" withMapping:imageMapping];
[objectManager.mappingProvider addObjectMapping:eventMapping];
[objectManager.mappingProvider setSerializationMapping:[eventMapping inverseMapping] forClass:[Event class]];
[objectManager.mappingProvider setMapping:eventMapping forKeyPath:#"bleedEvents"];
//********************************
//setup App user mapping
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[AppUser class]];
[userMapping mapAttributes:#"firstName", #"lastName", nil];
[userMapping mapRelationship:#"events" withMapping:eventMapping];
[objectManager.mappingProvider addObjectMapping:userMapping];
[objectManager.mappingProvider setSerializationMapping:[userMapping inverseMapping] forClass:[AppUser class]];
[objectManager.mappingProvider setMapping:userMapping forKeyPath:#"appUser"];
//********************************
//setup App user wrapper mapping
RKObjectMapping *userWrapperMapping = [RKObjectMapping mappingForClass:[AppUserWrapper class]];
[userWrapperMapping mapRelationship:#"appUser" withMapping:userMapping];
[objectManager.mappingProvider addObjectMapping:userWrapperMapping];
[objectManager.mappingProvider setSerializationMapping:[userWrapperMapping inverseMapping] forClass:[AppUserWrapper class]];
[objectManager.mappingProvider setMapping:userWrapperMapping forKeyPath:#"appUserWrapper"];
}
Here's how I convert the nested data structure to JSON:
-(void)convertTestUserToJSON
{
NSString* fullPath = [[$ documentPath] stringByAppendingPathComponent:#"sampleJSONFolder"];
[[NSFileManager defaultManager] createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil];
// Make the file
NSString* fullFilepath = [NSString stringWithFormat:#"%#_%#.json",appUser.firstName,appUser.lastName];
NSString* dataFile = [fullPath stringByAppendingPathComponent:fullFilepath];
appUser.localDataFilepath = dataFile;
[[AppUser managedObjectContext] save:nil];
NSError* error = nil;
RKObjectMapping *serMap = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[AppUser class]];
NSDictionary *d = [[RKObjectSerializer serializerWithObject:appUser mapping:serMap] serializedObject:&error];
if(error!=nil)
{
NSLog(#"!!!!! Error: %#",[error localizedDescription]);
}
//this is where the JSON is generated
NSString* dataContents = [d JSONString];
BOOL success = [dataContents writeToFile:dataFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
if(!success)
{
NSLog(#"Error writing to data file!");
}
}
Here's the result of the JSON conversion
{"firstName":"First Name0","events":[{"severity":0,"images":[{"createDate":"2005-04-21 08:28:47 +0000","localFilePath":"localPhoto#10.png"},{"createDate":"2009-12-19 07:26:54 +0000","localFilePath":"localPhoto#11.png"}],"createDate":"2003-05-25 15:32:53 +0000"},{"severity":1,"images":[{"createDate":"2008-08-02 19:40:14 +0000","localFilePath":"localPhoto#10.png"},{"createDate":"2007-01-12 05:50:27 +0000","localFilePath":"localPhoto#12.png"},{"createDate":"2007-05-28 12:19:39 +0000","localFilePath":"localPhoto#11.png"}],"createDate":"2001-05-28 23:38:23 +0000"},{"severity":2,"images":[{"createDate":"2002-10-09 19:47:39 +0000","localFilePath":"localPhoto#10.png"},{"createDate":"2008-08-17 03:21:12 +0000","localFilePath":"localPhoto#11.png"}],"createDate":"2005-10-18 03:40:52 +0000"}],"lastName":"Last Name83"}
When I'm trying to inflate this JSON back into a nested core data entity, I get an error:
CoreData: error: Failed to call designated initializer on NSManagedObject class 'AppUser'
2012-04-26 10:25:50.850 DropboxSync[3824:5843] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AppUser 0x2b7820> valueForUndefinedKey:]: the entity (null) is not key value coding-compliant for the key "lastName".'
It appears that the inflater does not instantiate all relationships before attempting mapping. Am I wrong? What could be causing this to happen? I've tried to re-inflate my JSON in two different ways, and both times I got this error. How can it be resolved?
Thank you for any input!
The problem is using RKObjectMapping instead of RKEntityMapping when dealing with Core Data, so RestKit's instantiating an NSManagedObject as regular NSObject.

Why doesn't Restkit parse correctly?

Why doesn't Restkit parse correctly?
Here my delegate call:
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObject:(id)object {
NSString *s = [[NSString alloc] initWithData:objectLoader.response.body encoding:NSUTF8StringEncoding];
RKObjectMapping *rm = objectLoader.objectMapping;
NSLog(#"%#",s);
NSLog(#"%#",rm);
NSLog(#"%#", object);
}
and here the output
{"device_id": "4f75c887e45b583e4f000004"}
RKObjectMapping class => ETDevice: keyPath mappings => (
"RKObjectKeyPathMapping: device_token => secret",
"RKObjectKeyPathMapping: deivce_id => identifier"
)
(null)
any reason why it shouldn't work?
I don't think is a problem of the way I make the http request but anyway here is the code:
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager sendObject:device delegate:self block:^(RKObjectLoader *loader) {
loader.serializationMapping = [[ETDevice objectMapper] inverseMapping];
loader.method = RKRequestMethodPOST;
loader.resourcePath = #"/devices/init";
loader.serializationMIMEType = #"application/json";
loader.objectMapping = [ETDevice objectMapper];
}];
btw I've also tried with:
{"device_id": "4f75c887e45b583e4f000004"}
{devices: {"device_id": "4f75c887e45b583e4f000004"}}
{devices: [{"device_id": "4f75c887e45b583e4f000004"}]}
please help, I'm timeboxing using restkit ;P
Ok I got completely crazy for this. I've debugged almost all the stack in Restkit.
The bug was just:
"deivce_id" instead of "device_id".
I've learned a lot about restKit at least

Rails RestKit POST request json's root class missing

I had this previous problem which I fixed, but I know the fix is def not done the right way. Can some one point me in the right direction for the right fix?
Basically I create a object with iOS sim via RESTkit postObject, and I got this message:
Processing PeopleController#create (for 127.0.0.1 at 2012-01-13 03:55:46) [POST]
Parameters: {"name"=>"data"}
Person Create (0.4ms) INSERT INTO "people" ("created_at", "updated_at", "name")
VALUES('2012-01-13 11:55:46', '2012-01-13 11:55:46', NULL)
Completed in 27ms (View: 1, DB: 0) | 200 OK
A nice gentlemen pointed out that my class inside my create function is only accepting a :person class which I overlooked.
def create
#person = Person.new(params[:person]) , thus looking for {"person" => {"name"=>"data"}}
I fixed this by doing
#person =Person.new(name:=>params[:name]) since i am only sending {"name"=>"data"}
now it creates ok, and I can see the entry on my ios sim. But I know this is not the right way. The right way should be {"person" => {"name"=>"data"}} sent in to the original function. Plus I get an error could not find an object mapping for keyPath:". Any thoughts?
Here is my xcode:
#interface Data: NSObject{// imaginary over arching class
Person * person;
NSArray *dog;
#property (nonatomic, retain) Person * person;
#property (nonatomic, retain) NSArray * dog;
#interface Data : NSObject {
Person *person;
NSArray *dogs;
}
#property (nonatomic ,retain) Person *person;
#property (nonatomic ,retain) NSArray *dogs;
#end
#interface Person : NSObject {
NSString *name;
NSNumber *personId;
NSDate *updatedAt;
NSDate *createdAt;
}
#property (nonatomic , retain) NSDate * createdAt;
#property (nonatomic , retain) NSDate * updatedAt;
#property (nonatomic , retain) NSNumber *personId;
#property (nonatomic , retain) NSString *name;
#end
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[Person class]];
[userMapping mapKeyPath:#"created_at" toAttribute:#"createdAt"];
[userMapping mapKeyPath:#"updated_at" toAttribute:#"updatedAt"];
[userMapping mapKeyPath:#"name" toAttribute:#"name"];
[userMapping mapKeyPath:#"id" toAttribute:#"personId"];
RKObjectMapping* dogMapping = [RKObjectMapping mappingForClass:[Dog class]];
[dogMapping mapKeyPath:#"created_at" toAttribute:#"createdAt"];
[dogMapping mapKeyPath:#"person_id" toAttribute:#"spersonId"];
[dogMapping mapKeyPath:#"name" toAttribute:#"name"];
[dogMapping mapKeyPath:#"updated_at" toAttribute:#"updatedAt"];
[dogMapping mapKeyPath:#"id" toAttribute:#"dogId"];
RKObjectMapping *dataMapping = [RKObjectMapping mappingForClass:[Data class]];
[dataMapping mapKeyPath:#"dog" toAttribute:#"dogs"];
[dataMapping mapKeyPath:#"person" toRelationship:#"person" withMapping:userMapping];
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:dataMapping];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/people"
objectMapping:dataMapping delegate:self];
RKObjectRouter * router = [RKObjectManager sharedManager].router;
[router routeClass: [Person class] toResourcePath:#"/people/:personId"];
[router routeClass: [Person class] toResourcePath:#"/people"
forMethod:RKRequestMethodPOST];
RKObjectMapping *personSerializationMapping = [RKObjectMapping mappingForClass:
[NSMutableDictionary class]];
[personSerializationMapping attribute:#"name", nil];
[RKObjectManager sharedManager].mappingProvider
setSerializationMapping:personalSerializationMapping forClass: [Person class]];
Person *dave = [[Person alloc]init];
dave.name = #"Dave";
[[RKObjectManager sharedManager] postObject:dave delegate:self];
}
Take Person as an example:
RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[Person class]];
[userMapping mapKeyPath:#"created_at" toAttribute:#"createdAt"];
[userMapping mapKeyPath:#"updated_at" toAttribute:#"updatedAt"];
[userMapping mapKeyPath:#"name" toAttribute:#"name"];
[userMapping mapKeyPath:#"id" toAttribute:#"personId"];
To config RestKit to POST with a root path, replace:
[[[RKObjectManager sharedManager] mappingProvider]
setSerializationMapping:personalSerializationMapping
forClass:[Person class]];
with:
[[[RKObjectManager sharedManager] mappingProvider]
registerMapping:objectMapping
withRootKeyPath:#"person"];
Now Rails will receive {"person" => {"name"=>"data"}} instead of {"name"=>"data"}.
For reference, here is the magic of registerMapping:
- (void)registerObjectMapping:(RKObjectMapping *)objectMapping withRootKeyPath:(NSString *)keyPath {
// TODO: Should generate logs
objectMapping.rootKeyPath = keyPath;
[self setMapping:objectMapping forKeyPath:keyPath];
RKObjectMapping* inverseMapping = [objectMapping inverseMapping];
inverseMapping.rootKeyPath = keyPath;
[self setSerializationMapping:inverseMapping forClass:objectMapping.objectClass];
}
it does both setMapping and setSerializationMapping with the clever [objectMapping inverseMapping] trick.
Yea finally figured it out. The mapping was wrong. Instead of addObjectMapping: dataMapping, it should have been userMapping with keyPath person which is userMapping. btw thnx #favo for editing my chicken scratch =] now it can GET and POST fine!
For completionist sake, the error about "could not find mapping for key path ''" seems to be a thing with Rails create action.
There's a good answer for this problem in this SO question: how to post an object to Rails using RESTKit