Late Loading of remote Data (RestKit and CoreData) - ios

I'm working a long time on this problem, but couldn't find a solution
that fits my needs.
The problem is, how could I load data and map them to the relationship
without loading the whole structure
Simple example:
We got some birds:
{
"birds":[{
"bird":{"id":"1","value":"LaLeLu"},
"bird":{"id":"2","value":"LeLeLa"},
...
}]
}
This could get mapped by something like this:
RKManagedObjectMapping *birdMapping = [RKManagedObjectMapping
mappingForClass:[Bird class]];
menuMapping.primaryKeyAttribute = #"identifier";
[menuMapping mapKeyPath:#"value" toAttribute:#"value"];
[menuMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[[[RKObjectManager sharedManager] mappingProvider]
setMappingForKeyPath:"birds.bird"];
Works great by now.
Now every bird could have a lot of comments - but I don't wanne load
all these comments with the first request.
Comments should get loaded when users clicks the specific bird.
So I request:
NSString *resourcePath = [NSString stringWithFormat:#"/birds/%#/
comments", myBird.id]
[[RKObjectManager sharedManager]
loadObjectsAtResourcePath:resourcePath];
I could change the response that it fits the needs of RestKit - but
what are the needs?
{
"comments":[{
"comment"{"id":"1","value":"Comment1","bird_id":"1"}
}]
}
And now I don't have an idea how to map this response.
Mapping the comments without any relation to the birds is no problem:
RKManagedObjectMapping *commentMapping = [RKManagedObjectMapping
mappingForClass:[Comment class]];
menuMapping.primaryKeyAttribute = #"identifier";
[menuMapping mapKeyPath:#"value" toAttribute:#"value"];
[menuMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[[[RKObjectManager sharedManager] mappingProvider]
setMappingForKeyPath:"comments.comment"];
Hope someone understands my problem and could help

For all who ware interested in the solution:
RestKit 0.10.0 fix the problem:
RKManagedObjectMapping *commentMapping = [RKManagedObjectMapping
mappingForClass:[Comment class]];
commentMapping.primaryKeyAttribute = #"identifier";
[commentMapping mapKeyPath:#"value" toAttribute:#"value"];
[commentMapping mapKeyPath:#"id" toAttribute:#"identifier"];
// Here starts the relevant part:
[commentMapping mapKeyPath:#"bird_id" to Attribute:#"bird_id"];
[commentMapping mapRelationship:#"bird" withMapping:birdMapping];
[commentMapping connectRelationship:#"bird" withObjectPropertyForPrimaryKeyAttribute:"bird_id"]
[[[RKObjectManager sharedManager] mappingProvider]
setMapping:commentMapping ForKeyPath:"comments.comment"];

Related

Having trouble doing a POST request with RESTKit

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

RestKit 0.10.0 with foursquare API's not retrieving the response when using blocks

Um beginner with RestKit, first example for me was on foursquare API's and I've used RestKit with Blocks not delegates.
I want to retrive the name's of venues,this is the JSON response
and this is my code:
// App Delegate.m
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURLString:#"https://api.Foursquare.com/v2"];
RKManagedObjectStore *objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"Venue.sqlite"];
objectManager.objectStore = objectStore;
objectManager.serializationMIMEType = RKMIMETypeJSON;
RKManagedObjectMapping *venueMapping = [RKManagedObjectMapping mappingForClass:[Venue class] inManagedObjectStore:objectStore];
[venueMapping mapKeyPath:#"id" toAttribute:#"id"];
[venueMapping mapKeyPath:#"name" toAttribute:#"name"];
venueMapping.primaryKeyAttribute = #"id";
[objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"response.venue"];
then in myViewController.m
-(void)loadVenues{
// When caling loadObjectsAtResourcePath method it specify RKObjectLoader which is the actual request.
// within these block you can take more options to controll the request.
[[RKObjectManager sharedManager]loadObjectsAtResourcePath:#"/venues/40a55d80f964a52020f31ee3?oauth_token=FNQPN5P5EKLJ5IQ44TMWO00I3W033M0Y1TKINW2OTF2VIOTP&v=20130512" usingBlock:^(RKObjectLoader* loader)
{
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Venue class]];
loader.onDidLoadObject = ^(NSArray *objects)
{
NSLog(#"onDidLoadObject Blocks");
self.data = objects;
[self.tableView reloadData];
};
}
];
}
and the app is entering the block of onDidLoadObject but every time the array is empty !!
even when I test the link on browser it comes with data.
When I debug the loader.URL it always come with these
https://api.Foursquare.com/v2/venues/40a55d80f964a52020f31ee3?v=20130512&oauth_token=FNQPN5P5EKLJ5IQ44TMWO00I3W033M0Y1TKINW2OTF2VIOTP -- https://api.Foursquare.com/v2 -- https://api.Foursquare.com/v2
I don't know why load.URL is wrong ?!
I think um calling the 4square API's with the wrong way, anyone can help ? :)
-Put Your mapping as a class method to be accessed from all application classes, and done only once.
-change the Attribute "id" as it is reserved in Objective-C.
-add this to the block
[loader setObjectMapping:[RestKitMapping yourMapping]];
-then add this with your mapping code
[objectManager.mappingProvider setMapping:venueMapping forKeyPath:#"response.venue"];
-And use Delegates instead of blocks
I've two concerns regarding the above code:
1- Apparently the above JSON response, lists just one Venue .. So KeyPath should be "response.venue" not "response.venues"
2- Where's the mapping for ID? .. which is the primary key that RestKit uses to insert into DB? You need to set the primary key mapping.

RestKit Pagination

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.

Mapping object without root key to dynamic url?

I'm attempting to map the following object:
{"walletAccepted":false,"creditCardTypesAccepted":["visa","mastercard","discover","americanexpress"],"paypalAccepted":false}
To an object with identical attributes. However, the ResourcePath is dynamic, in that it's along the lines of /paymentmethods/zone/:internalZoneCode
Every attempt I've made at mapping it always ends up with "Encountered errors during mapping: Could not find an object mapping for keyPath: ''"
I think the issue is that there is no root key/key path. I've also attempted the following with no luck:
RKURL *rkUrl = [RKURL URLWithBaseURL:[RKClient sharedClient].baseURL resourcePath:[NSString stringWithFormat:#"/paymentoptions/zone/%#", zoneNumber]];
RKObjectLoader* loader = [[RKObjectLoader alloc] initWithURL:rkUrl mappingProvider:[RKObjectManager sharedManager].mappingProvider];
loader.method = RKRequestMethodGET;
loader.delegate = self;
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[PMZonePaymentMethods class]];
[loader send];
I found the way to make it happen is to configure the mapping, and add the mapping to the object manager without a key path or resourcek
So like this:
RKObjectMapping* zonePaymentMethodsMapping = [RKObjectMapping mappingForClass:[PMZonePaymentMethods class]];
[zonePaymentMethodsMapping mapKeyPath:#"walletAccepted" toAttribute:#"walletAvailable"];
[zonePaymentMethodsMapping mapKeyPath:#"creditCardTypesAccepted" toAttribute:#"cards"];
[zonePaymentMethodsMapping mapKeyPath:#"paypalAccepted" toAttribute:#"paypalAvailable"];
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:zonePaymentMethodsMapping];
and use this to make the request:
RKURL *rkUrl = [RKURL URLWithBaseURL:[RKClient sharedClient].baseURL resourcePath:[NSString stringWithFormat:#"/paymentoptions/zone/%#", zoneNumber]];
RKObjectLoader* loader = [[RKObjectLoader alloc] initWithURL:rkUrl mappingProvider: [RKObjectManager sharedManager].mappingProvider];
loader.method = RKRequestMethodGET;
loader.delegate = self;
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[PMZonePaymentMethods class]];
[loader send];

Restkit sending Get request instead of Post

I'm just starting to use Restkit and am trying to send a regular POST request "serverUrl/account/authenticate.xml?OPTIONS" to get a response as XML. This is the code I use to call the URL:
-(void) function{
// OBJECT MANAGER
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:serverUrl];
[manager.router routeClass:[CBUser class] toResourcePath:#"/account/authenticate" forMethod:RKRequestMethodPOST];
// OBJECT MAPPING
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[CBUser class]];
mapping = [manager.mappingProvider objectMappingForKeyPath:#"user"];
[mapping mapKeyPath:#"id" toAttribute:#"userId"];
[manager loadObjectsAtResourcePath:str objectMapping:mapping delegate:self];
}
// DELEGATE
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
CBUser* user = [objects objectAtIndex:0];
NSLog(#"Loaded Contact ID #%# -> Firstname: %#, Lastname: %#", user.userId, user.firstname, user.lastname);
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
NSLog(#"Encountered an error: %#", error);
}
Looking at the server log it seems it receives a GET request instead of a POST even though I'm using the "RKRequestMethodPOST" option.
Here is my error form my logs:
Started GET "/account/authenticate.xml?commit=Login&authenticity_token=iPhone&user%5Bpassword=XXXXX&user%5Bemail=XXXXXXX%5D&user%5Bdevice_token=XXXXXXX" for 192.168.106.30 at 2012-01-25 19:20:03 -0800
AbstractController::ActionNotFound (The action 'show' could not be found for AccountController):
Rendered /Users/Guillaume/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.0.9/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb within rescues/layout (1.2ms)
What am I missing?
Hopefully this code snippet of my app will help. You setup your mappings, say in your delegate. Note the "forMethod"
RKObjectRouter *router;
RKObjectMapping* createAccountSerializationMapping = [RKObjectMapping mappingForClass:[Cr eateAccount class]];
[createAccountSerializationMapping mapAttributes:#"email", #"pwd", #"uname", nil];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:createAccountSerializationMapping forClass:[CreateAccount class]];
router = [RKObjectRouter new] ;
[router routeClass:[CreateAccount class] toResourcePath:#"/registration/rest/users/create_account" forMethod:RKRequestMethodPOST];
[RKObjectManager sharedManager].router = router;
and then later, when you want to post an object
[[RKObjectManager sharedManager] postObject:user delegate:self];

Resources