So the problem is that when I'm trying to load entity from here I'm not getting things to work. My Pathpatterns seems to be wrong.
Here's my Mapping and Descriptor:
RKEntityMapping *statsMapping = [RKEntityMapping mappingForEntityForName:#"Stat" inManagedObjectStore:managedObjectStore];
[statsMapping addAttributeMappingsFromDictionary:#{
#"sort_id" : #"sortID",
#"id" : #"statID",
#"deleted" : #"deletedFlag",
#"created_at": #"createdAt",
#"updated_at": #"updatedAt"
}];
statsMapping.identificationAttributes = #[ #"statID" ];
[statsMapping addAttributeMappingsFromArray:#[ #"title"]];
RKEntityMapping *featuresMapping = [RKEntityMapping mappingForEntityForName:#"Feature" inManagedObjectStore:managedObjectStore];
[featuresMapping addAttributeMappingsFromDictionary:#{
#"sort_id" : #"sortID",
#"id" : #"featureID",
#"deleted" : #"deletedFlag",
#"created_at": #"createdAt",
#"updated_at": #"updatedAt",
}];
featuresMapping.identificationAttributes = #[ #"featureID" ];
[featuresMapping addAttributeMappingsFromArray:#[ #"title", #"value"]];
[statsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"features" toKeyPath:#"features" withMapping:featuresMapping]];
RKResponseDescriptor *statsDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:statsMapping
pathPattern: #"/api/cars/:carID/features.json"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsFromArray:#[newsDescriptor, catalogDescriptor, statsDescriptor]];
So when I use pathPattern:nil it works, but if no answer is returned by url it just tries to put another responsedescriptor to the response and gives me random data :)
The question is, if I have the car ID in the middle of the pattern, how should I declare it?
Thank you!
Edit1: This is how I do request:
- (void)getStats:(NSNumber *)carID
{
[[RKObjectManager sharedManager] getObjectsAtPath:[NSString stringWithFormat:#"api/cars/%#/features.json", carID]
parameters:#{#"auth_token" : [Constants authToken]}
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load complete: Stats loaded");
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Load failed with error: %#", error);
[self showError:error withDelay:3];
}];
}
From your trace log there is some problem with your managed objects, not your mappings. Your entities are obviously defined in the model and with the correct names and with the appropriate attributes. So it looks like either you created classes for the entities but wrongly or that the object manager isn't being provided with an / the correct object store reference.
Your log contains CoreData: error: Failed to call designated initializer on NSManagedObject class 'Stat' and [<Stat 0xa68b7c0> valueForUndefinedKey:]: the entity (null) is not key value coding-compliant for the key "statID". which are both issues to do with the entity creation / referencing. It isn't clear how that happens based on the code posted.
Try changing your path pattern to remove the leading slash:
#"api/cars/:carID/features.json"
when defining your statsDescriptor as that can cause the pattern to not match.
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:.
I'll try to explain how I want the mapping done:
TOP lvl json object contains Players object which is an array of Player objects
each Player object contains an array, I want each of those objects in the array to be of an Event object (custom object).
now since I have a mapping of the Player object and i'm getting the array filled, tho instead of Event objects(which is what I want), i'm getting NSDictionary objects. thing is that I do have a mapping of my Event class. my issue is getting the RestKit to map these into the array.
I've tried adding responseDescriptors of an Event class tho i've had no luck.
Here is the Player object mapping
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
...more here
#"activeEvents" : #"activeEvents"
}];
here is the request method
NSURL *taskURL = [NSURL URLWithString:kAppWebApiURLPath];
// Set object manager with base url
RKObjectManager *objectManager = [RKObjectManager sharedManager];
objectManager = [RKObjectManager managerWithBaseURL:taskURL];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
[objectManager.HTTPClient setDefaultHeader:#"Authorization" value:kAppWebAPIKey];
[objectManager.HTTPClient setDefaultHeader:#"Content-Type" value:#"application/json"];
RKRequestDescriptor * requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[MappingProvider inverseLoginMapping] objectClass:[LoginInfo class] rootKeyPath:nil method:RKRequestMethodPOST];
[objectManager addRequestDescriptor:requestDescriptor];
RKResponseDescriptor *playersResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider playerMapping] method:RKRequestMethodGET pathPattern:nil keyPath:#"players" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:playersResponseDescriptor];
NSLog(#"%#",loginInfo.iOSDeviceToken);
[objectManager postObject:loginInfo path:kAppWebApiLoginPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{}...
Update, I now need one step further of mapping, My player object contains an array of events which I successfully mapped using
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:[MappingProvider eventMapping]];
yet now each of those Event objects contains an array of Players, so its like Players -> Events -> Players.
Here is the Mapping for both Event and Player objects :
RKObjectMapping* eventMapping = [RKObjectMapping mappingForClass:[Event class]];
[eventMapping addAttributeMappingsFromDictionary:#{
#"stuffhere" : #"stuffz"
}];
RKObjectMapping* playerMapping = [RKObjectMapping mappingForClass:[Player class]];
[playerMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"activeEvents" : #"activeEvents"
}];
[eventMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents/players" mapping:playerMapping];
now I don't get a recursive function, but how do I state in code to make that relationship
mapping of the json array to assign to my local array property ?
Remove #"activeEvents" : #"activeEvents" from the mapping and replace it with:
[playerMapping addRelationshipMappingWithSourceKeyPath:#"activeEvents" mapping:eventMapping];
You should also only have one response descriptor because the data is nested.
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).
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 stuck with a pagination issue.
When I go to /?PageSize=:perPage&Page=:page" I will get a json response like this:
Which I would like to map using the following paginator:
/* BBActivityPaginator */
RKObjectMapping *activityPaginationMapping = [RKObjectMapping mappingForClass:[BBActivityPaginator class]];
[activityPaginationMapping addAttributeMappingsFromDictionary:#{
#"Page" :#"currentPage",
#"PageSize" :#"perPage",
#"TotalResultCount" :#"objectCount"
}];
[activityPaginationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"PagedListItems" toKeyPath:#"activities" withMapping:activityMapping]];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:activityPaginationMapping
pathPattern:#"/?PageSize=:perPage&Page=:page"
keyPath:#"Model.Activities"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
which is related to this mapping:
/* BBActivity */
RKObjectMapping *activityMapping = [RKObjectMapping mappingForClass:[BBActivity class]];
[activityMapping addAttributeMappingsFromDictionary:#{
#"Id" :#"identifier",
#"CreatedDateTime" :#"createdOn",
#"Description" :#"description",
#"CreatedDateTimeOrder" :#"order",
#"Type" :#"type",
#"DeletedActivityItem.Message" :#"deleted"
}];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"User" toKeyPath:#"user" withMapping:userMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"ObservationAdded.Observation" toKeyPath:#"observation" withMapping:observationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"SightingNoteAdded.SightingNote" toKeyPath:#"observationNote" withMapping:observationNoteMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"SightingNoteAdded.Sighting" toKeyPath:#"observationNoteObservation" withMapping:observationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"IdentificationAdded.Sighting" toKeyPath:#"identificationObservation" withMapping:observationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"IdentificationAdded.Identification" toKeyPath:#"identification" withMapping:identificationMapping]];
[activityMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"PostAdded.Post" toKeyPath:#"post" withMapping:postMapping]];
[manager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:activityMapping
pathPattern:nil
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
... For brevity I won't add all the mappings...
When I want to make my call to the paginator, I'm wiring up this:
-(void)setPaginatorForStream:(NSString*)streamName {
[BBLog Log:#"BBStreamController.setPaginatorForStream:"];
[BBLog Debug:#"streamName:" withMessage:streamName];
__weak typeof(self) weakSelf = self;
NSString *streamUrl = [NSString stringWithFormat:#"http://api.blahblah.org.au/%#?PageSize=:perPage&Page=:currentPage&X-Requested-With=XMLHttpRequest", streamName];
if (!self.paginator) {
paginationMapping = [RKObjectMapping mappingForClass:[BBActivityPaginator class]];
RKResponseDescriptor *activitiesResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[RKObjectMapping mappingForClass:[BBActivityPaginator class]]
pathPattern:nil
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
self.paginator = [[BBActivityPaginator alloc]initWithRequest:[NSURLRequest requestWithURL:[[NSURL alloc]initWithString:streamUrl]]
paginationMapping:paginationMapping
responseDescriptors:[[NSArray alloc]initWithObjects:activitiesResponseDescriptor, nil]
andDelegate:weakSelf];
}
self.paginator.perPage = 20;
[self.paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) {
[weakSelf.tableItems addObjectsFromArray:objects];
[weakSelf.tableView reloadData];
} failure:^(RKPaginator *paginator, NSError *error) {
NSLog(#"Failure: %#", error);
}];
Which is being activated in this method:
-(void)loadRequest {
[BBLog Log:#"BBStreamController.loadRequest"];
self.fetchBatch++;
[self.paginator loadPage:self.fetchBatch];
//[self.paginator setPaginatorLoading:YES];
self.loading = YES;
}
from the class initialisation:
-(BBStreamController*)initWithGroup:(NSString*)groupIdentifier
andDelegate:(id<BBStreamProtocol>)delegate {
[BBLog Log:#"BBStreamController.initWithGroup:andDelegate:"];
self = [self init];
if(self) {
_controller = delegate;
groupId = groupIdentifier;
[self setPaginatorForStream:groupIdentifier];
[self loadRequest];
}
[self loadView];
return self;
}
And getting nothing but pain...
Output:
E restkit:RKPaginator.m:207 Paginator didn't map info to compute page count. Assuming no pages.
2013-05-06 17:48:09.487 BowerBird[26570:5807] W restkit.object_mapping:RKMapperOperation.m:98 Adding mapping error: No mappable values found for any of the attributes or relationship mappings
Any one familiar with the finer details of RestKit 0.2.x's new pagination?
Your pathPattern and keyPath information needs to be set on the activitiesResponseDescriptor definition, not some paginator response descriptor that isn't actually used for anything.
The response descriptor is for the overall response and describes how to:
Tell that we have a match : pathPattern
Find the data to process : keyPath
Create the response objects : data mapping
The paginator mapping is additional to this and is only used to extract the page data from the response being processed.
Move the path pattern and key path to the response descriptor used for the request. The path pattern should also contain "PagedListItems" by the looks of it.