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:.
Related
This is my response from API:
{"is_favorited":1}
I want to map it by RestKit but I can't get it working. This I have in AppDelegate.m:
RKObjectMapping *isFavoriteMapping = [RKObjectMapping mappingForClass:[IsFavorite class]];
[isFavoriteMapping addAttributeMappingsFromDictionary:#{
#"is_favorited" : #"Is_favorited",
}];
RKResponseDescriptor *isFavoriteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:isFavoriteMapping
method:RKRequestMethodAny
pathPattern:#"/api/isPointFavorited/:sid"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:isFavoriteResponseDescriptor];
Path is okay. I have token in address. I am calling it by:
RKObjectManager *manager = [RKObjectManager sharedManager];
[manager getObjectsAtPath: [NSString stringWithFormat:#"/api/isPointFavorited/%#", sid]
parameters:#{
#"pid":[NSString stringWithFormat:#"%#", actualPlace.Id]
}
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
...
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
...
}];
I am using RestKit several times in app and with sid (login token) too. So there is no problem with address or method call. Problem must be in mapping but I don't know how to do it. I tried to change keyPath from nil to #"is_favorited" but it didn't help.
My Is_favorite.h:
#interface IsFavorite : NSObject
#property(nonatomic) NSNumber *Is_favorited;
#end
RestKit error:
Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "No mappable object representations were found at the key paths searched." UserInfo=0x1849cbc0 {DetailedErrors=(
), NSLocalizedFailureReason=The mapping operation was unable to find any nested object representations at the key paths searched: events, results
The representation inputted to the mapper was found to contain nested object representations at the following key paths: is_favorited
This likely indicates that you have misconfigured the key paths for your mappings., NSLocalizedDescription=No mappable object representations were found at the key paths searched., keyPath=null}
Edit - changes in AppDelegate but still not working:
RKObjectMapping *isFavoriteMapping = [RKObjectMapping mappingForClass:[IsFavorite class]];
[isFavoriteMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"Is_favorited"]];
RKResponseDescriptor *isFavoriteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:isFavoriteMapping
method:RKRequestMethodAny
pathPattern:#"/api/isPointFavorited/:sid"
keyPath:#"is_favorited"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[manager addResponseDescriptor:isFavoriteResponseDescriptor];
Remove you usage of addAttributeMappingsFromDictionary:, and change to:
[isFavoriteMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"Is_favorited"]];
then change the keyPath on the response descriptor to #"is_favorited".
This is known as a nil key path mapping (because of the nil source key path in the mapping).
The response object for a POST request crashes while trying to map to an object. The following output is printed “CoreData: error: Failed to call designated initializer on NSManagedObject class mapping operation”
As the message suggests a RKObjectRequestOperation is being used rather than a RKManagedObjectRequestOperation. My problem may be complicated since the outer object is a normal nsobject but within it there's a member variable that contains an nsmanagedObject entity. I’ve been spelunking through the code and I’m not sure how to steer the code into the direction I want. What’s even more galling is that I’m able to map to the response object for a GET request. The response object is essentially the same with the exception. Here’s the code I’m using
My response object is a normal NSObject with a nested managed object within it.
// response definition
#interface PDResponse : NSObject
#property (nonatomic, strong) NSString *message;
#property (nonatomic, strong) NSNumber *success;
#property (nonatomic, strong) id value;
#property (nonatomic, strong) NSDictionary *exception;
#property (nonatomic, strong) NSString *transactionId;
#end
// Mapping for the download object
RKEntityMapping * repMapping = [RKEntityMapping mappingForEntityForName:#"PDRepresentative" inManagedObjectStore:managedObjectStore];
repMapping = #[#"repId"];
[repMapping addAttributeMappingsFromDictionary:#{
#"RepId": #"repId",
#"First": #"firstName",
#"Middle": #"middleName",
#"Last": #"lastName",
#"Address1": #"address1",
#"Address2": #"address2",
#"City": #"city",
#"Zip": #"zip",
#"Country": #"country",
#"Company": #"company",
#"Phone": #"phone",
#"PhonePersonal": #"phonePersonal",
#"Fax": #"fax",
#"Email": #"email",
#"EventId": #"event.eventId",
#"Specialty": #"specialty"}];
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[PDResponse class]];
[responseMapping addAttributeMappingsFromDictionary:#{#"Message": #"message",
#"Success": #"success",
#"Ex": #"exception",
#"TransactionId": #"transactionId"}];
[responseMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Value"
toKeyPath:#"value"
withMapping:repMapping]];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping: responseMapping
method:RKRequestMethodPOST
pathPattern:#"PostAddOrUpdateRep/"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// POST Object
RKObjectManager *objectManager = [RKObjectManager sharedManager];
NSMutableURLRequest *request = [objectManager requestWithObject:self method:RKRequestMethodPOST path:#"PostAddOrUpdateRep/" parameters:nil];
RKObjectRequestOperation *operation = [objectManager objectRequestOperationWithRequest:request
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
PDResponse *response = mappingResult.firstObject;
response = [response isKindOfClass:[PDResponse class]] ? response : nil;
TRACE_LOG(#"%#", response);
}
failure:^(RKObjectRequestOperation *operation, NSError *error){
ERROR_LOG(#"Failure int %s %#, %#", __PRETTY_FUNCTION__, operation, error);
}];
[objectManager enqueueObjectRequestOperation:operation];
self.sent = #(YES);
I've observed many other posts and have done the following in accordance to the info I found from those posts.
It was suggested in another post to remove and add slashes due to problems with descriptors not being able to map to paths properly but that wasn’t the case. I opted to not use the postObject:path:parameters:success:failure function since it was giving me an error for mapping the response where the target object variable was assigned to of type PDRepresentative rather than of PDResponse.
I'm also able to properly upload, so my managedObjectStore is fine. As you can see I'm using a RKEntityMapping object so, one would hope that this would tell the code to use a NSManagedObject rather than NSObject.
A user named, Wain, suggested in another post that mixing managed and non managed items could pose a potential problem. I'm inclined to believe him but I wanted to through this out there since I did get it to work for my response from my GET request.
Any help would be greatly appreciated thank you.
I was being foolish and simply changed the function that created the request operation to managedObjectRequestOperationWithRequest:managedObjectContext:success:fail rather than using objectRequestOperationWithRequest:success:fail. I simply read what I wrote in the first paragraph of my question.
God I'm a moron.
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 trying to post some data (an authentication token for a website, to be specific) with RESTKit and I'm having trouble. Each time I run the method, I get this:
'NSInvalidArgumentException', reason: '`RKRequestDescriptor` objects must be initialized
with a mapping whose target class is `NSMutableDictionary`, got 'Login' (see
`[RKObjectMapping requestMapping]`)'
Also, there are many 'Incompatible pointer types' warnings in the code.
Here is my method which is really ugly and bad, and I would like some help fixing up. I do a [self postToken] inside an IBAction method when a button is pressed. I am getting variable userAuthToken from another file, where it is set using with the json response from a POST request not using RESTKit. I will eventually convert that POST to RESTKit when I know how to successfully. The method "userAuthTokenMethod" is in the same file as the "postToken" method, and it allows me to use the userAuthToken object, which is initialized in AppDelegate so it acts as a global variable.
- (void)postToken
{
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Login class]];
[mapping addAttributeMappingsFromDictionary:#{#"token": #"token"}];
NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *tokenResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:nil pathPattern:nil keyPath:nil statusCodes:statusCodeSet];
RKRequestDescriptor *tokenRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:self rootKeyPath:nil method:nil];
[[RKObjectManager sharedManager] addRequestDescriptor:tokenRequestDescriptor];
RKObjectManager *tokenManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"https://websitetest.com/doctors/find"]];
[tokenManager addResponseDescriptor:tokenResponseDescriptor];
[tokenManager addRequestDescriptor:tokenRequestDescriptor];
[tokenManager postObject:[[self userAuthTokenMethod] userAuthToken] path:nil parameters:nil success:nil failure:nil];
NSURL *tokenURL = [NSString stringWithFormat:#"https://websitetest.com/doctors/find?name=%#&location=%#",nameIDTextField.text, locationTextField.text];
NSMutableURLRequest *tokenRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tokenURL]];
RKObjectManager *tokenObjectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:tokenURL]];
[tokenObjectManager.HTTPClient setDefaultHeader:#"Auth-Token" value:[[self network360Edge] userAuthToken]];
RKObjectRequestOperation *tokenOperation = [[RKObjectRequestOperation alloc] initWithRequest:tokenRequest responseDescriptors:#[tokenResponseDescriptor]];
NSLog(#"Token being POSTed ==> %#", [[self userAuthTokenMethod] userAuthToken]);
[tokenOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result){
NSLog(#"Response for POST request with token ==> %#", [result array]);
}failure:nil];
[tokenOperation start];
}
Here is the code for my mapping provider file called MappingProvider:
//MappingProvider.h
#import <Foundation/Foundation.h>
#import <RestKit/RestKit.h>
#interface MappingProvider : NSObject
+(RKMapping *)tokenMapping;
#end
//MappingProvider.m
#import "MappingProvider.h"
#import <RestKit/RestKit.h>
#import "Login.h"
#implementation MappingProvider
+(RKMapping *)tokenMapping
{
RKObjectMapping *tokenMapping = [RKObjectMapping mappingForClass:[Login class]];
[tokenMapping addAttributeMappingsFromArray:#[#"token"]];
return tokenMapping;
}
#end
Here is the code for the Login file where 'token' is initialized:
//Login.h
#import <Foundation/Foundation.h>
#interface Login : NSObject
#property (nonatomic, copy)NSString *token;
#end
I'm very new to RESTKit and somewhat new with Objective-C (I've been coding in it for about a month). I would appreciate all help in fixing up my code.
This question is old, but the issue is that requests should use mapping without class specification, because requests use descriptors that specify class (see 2 lines below), like this:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
Probably you also have issue in line:
RKRequestDescriptor *tokenRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:self rootKeyPath:nil method:nil];
more concretely in objectClass:self, it should be objectClass:[self class] or the needed class that contains token property, like this:
RKRequestDescriptor *tokenRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[self class] rootKeyPath:nil method:nil];
Hope this helps
I'm currently using the RKErrorMessage class to map an error message from my server like so:
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:#"message" toKeyPath:#"errorMessage"]];
RKResponseDescriptor *errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
pathPattern:nil
keyPath:#"error"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
But now I would like to map an additional parameter from my server, for instance the error title (or anything else for that matter). My JSON looks like this:
{
"error":{
"title":"This is the error title"
"message":"This is the error message",
}
}
The idea is that I can directly map some of these error messages to UIAlertViews if something ever goes wrong when my app is accessing the server. This can be done already with only the error message but adding a title seems to make it fit well with UIAlertView.
From looking at the RKErrorMessage class I see that it only has two parameters: errorMessage and userInfo. How would I go about doing this?
UPDATE: The reason I'm trying to do this with RKErrorMessage is that it automatically maps to the NSError passed into the failure block in the RestKit get/post/put/etc methods... which is pretty nice.
[objectManager getObjectsAtPath:#"/api/somethingsomething/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
}];
Here's a way to do what you originally asked (not sure why you accepted the answer that didn't actually answer your question):
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"userInfo"]];
RKResponseDescriptor *errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
pathPattern:nil
keyPath:#"error"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
The key difference from your sample code is this line:
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"userInfo"]];
Then, from the failure block callback, you can access the userInfo dictionary like:
NSString *message = [[[[[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey] objectAtIndex:0] userInfo] objectForKey:#"message"];
NSString *title = [[[[[error userInfo] objectForKey:RKObjectMapperErrorObjectsKey] objectAtIndex:0] userInfo] objectForKey:#"title"];
RestKit will automatically map all attributes of the JSON's "error" dictionary to the RKErrorMessage's userInfo dictionary.
Why are you using RKErrorMessage ???
The RKErrorMessage is a simple class used for representing error
messages returned by a remote backend system with which the client
application is communicating.
I think the best way is to create the custom object to map your paramteres.
#interface MyCustomErrorObject : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *message;
#end