is there a way to map the parameters in an URL to the results?
I got a rest service where the user can search something by its ID. The path is search/:id
The results contains the name and other properties but not the ID.
I could do something like this:
NSArray *array = mappingResult.array;
for (Item *item in array) {
[item setId:itemID];
}
but I hope there is a nicer way...
Thanks for any hints
Xean
You want to use the path pattern you specify in your response descriptor. Then you want to use routing (RKRoute) and metadata during your mapping. The metadata includes a routing section which gives access to the parameters extracted from the URL path.
Some info on metadata here (the docs are a little lacking).
In your mapping you want to use:
#metadata.routing.parameters.id
As the mapping source key path.
To make routing work you need to add the route to your object manager:
[manager.router.routeSet addRoute:...
And then you need to make the request in a way that means the route is used, like getObjectsAtPathForRouteNamed:object:parameters:success:failure:.
You probably need to specify the pathPattern and keyPath of your response descriptor::
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor
responseDescriptorWithMapping:itemMapping
method:RKRequestMethodAny
pathPattern:#"search/:id"
keyPath:#"item"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
Related
I'm trying to use RestKit because I'm expecting to make Core Data managed objects out of requests responses and it seemed like the framework was all about doing that and it seemed to be rather full featured.
But I'm having trouble getting my POST /user/login with parameters api_key=<value> (plus a separate JSON body) to end up a going out in a request like /user/login?api_key=<value>.
In the internals of RKObjectManager, requestWithMethod:path:parameters: does:
// NOTE: If the HTTP client has been subclasses, then the developer may be trying to perform signing on the request
NSDictionary *parametersForClient = [self.HTTPClient isMemberOfClass:[AFHTTPClient class]] ? nil : parameters;
request = [self.HTTPClient requestWithMethod:method path:path parameters:parametersForClient];
Do I have it right that this AFNetworking superclass method encodes parameters into the URL query? And does this mean the only way to ensure parameters are passed to that is to have my RKObjectManager use some subclass of AFHTTPClient?
And, according to the comment, supposedly this is only for sake of maybe a fringe case, something about request signing or something? Why is URL query-encoded request parameters not a common thing to do??
And getting things JSON encoded like I want does not seem to be as easy as I'd hoped either. Maybe it's a mistake for me to even try to use RestKit.
So the current service I'm using for updating an object follows the formatting of the following URL
http://www.baseurl.com/servicemethodAddObject/transactionId?clientTime=clientTime
So this is the code I put in there. Roughly.
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:eventResponseMapping method:RKRequestMethodGET pathPattern:#"/GetStuff" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// I'd want to add items to the URL but the parameters parameter only goes in to the post body, If I add objects to the path #"?GetStuff/?parm=Stuff&parm2=stuff2" then the response will not be mapped properly
[objectManager postObject:object path:#"/GetStuff" parameters:parms success:nil failure:nil];
It seems that RestKit requires that the path used to post the object and the pathPattern used to create the RKDescriptor must be an exact match. Is there a way to get restKit to do a comparison that ignores the url parameters for it's object mapping?
Is this even the right thing to do? Posting an object and adding items in the URL.
You can build the path to add your parameters. Using pathPattern:#"/GetStuff" will match against built paths like:
#"/GetStuff?parm=Stuff&parm2=stuff2"
(you will have issues if you add or remove / characters that aren't in your path pattern)
https://github.com/RestKit/RestKit/issues/1877
Apparently the solution is to not do it.
I'm developing a CoreData iOS app that's backed by a (Rails) REST API (that supports shallow routes). Because there are a lot of objects in the graph, I'd like the REST GETs to not to include a lot of nested results and, instead, just contain references that RestKit uses to establish (faulted) relationships. I'll then use shallow routes to request the individual (or groups of) objects as needed.
Assuming I have a one-to-many (<-->>) data model such as A <-->> B, I have:
RKEntityMapping *a_mapping = [RKEntityMapping mappingForEntityForName:#"A" inManagedObjectStore:managedObjectStore];
[a_mapping addAttributeMappingsFromDictionary:#{#"a_id" : #"aId", ...}];
a_mapping.identificationAttributes = #[#"aId"];
RKEntityMapping *b_mapping = [RKEntityMapping mappingForEntityForName:#"B" inManagedObjectStore:managedObjectStore];
[b_mapping addAttributeMappingsFromDictionary:#{#"b_id" : #"bId", ...}];
b_mapping.identificationAttributes = #[#"bId"];
[a_mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"bs" toKeyPath:#"bs" withMapping:b_mapping]];
I have these routes:
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKResponseDescriptor *a_ResponseDescriptor;
a_ResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:a_mapping method:RKRequestMethodGET pathPattern:#"/A/:aId" keyPath:#"A" statusCodes:statusCodes];
RKResponseDescriptor *b_ResponseDescriptor;
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
b_ResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:b_mapping method:RKRequestMethodGET pathPattern:#"/B/:bId" keyPath:#"B" statusCodes:statusCodes];
[[RKObjectManager sharedManager] addResponseDescriptor:a_ResponseDescriptor];
[[RKObjectManager sharedManager] addResponseDescriptor:b_ResponseDescriptor];
I have a couple of related questions:
How should I structure the JSON when returning an 'A' record so that RestKit will instantiate stubs for any related 'B' objects?
Similarly, if I want to request a bunch of B objects (without prior knowledge of A objects) how do I structure the JSON when returning a 'B' record so that RestKit will instantiate stubs for the owning 'A' object?
What additional setup/code do I need with RestKit?
Currently, I have one direction working (A --> B), but I can't seem to figure out how to get the reverse to work. In particular, /A/1.json returns something like:
{"a": {"a_id":1, "bs":[{"b_id": 2}, {"b_id": 3}]}}
And B/2.json returns:
{"b": {"b_id":2, "a_id": 1}}
Should I instead be using something like:
{"b": {"b_id":2, "a": {"a_id": 1}}}
? Any help/advice would be appreciated.
Faults are a Core Data concept. RestKit is involved with creating objects in the data store, but it is the act of fetching them from the data store which faults them. It sounds more like what you're interested in is having RestKit create 'stub' objects in the data store (objects with the correct id and relationship but no detailed attributes set) so they can be filled in later when required.
Your current mappings and JSON for /A/1.json are fine for creating a stub instance of A and stubs of the connected Bs.
B/2.json would only create a stub B (it wouldn't do anything with A as the mapping has no relationship information attached). It's the mapping that is at 'fault', not the JSON. Add an RKRelationshipMapping to the mapping for B and the stub would be created and connection made.
That said, you would not usually be making a request for B which required 'back-stubbing' of A, because you would usually need to have requested A in order to get B.
Finally, you don't need different mappings for stubs and detail. Just have 1 mapping which contains all the detail and id mappings and RestKit will do everything that it can based on the received JSON (so if the JSON only contains ids you will just get stub objects).
To build off of what Wain said, the solution is to:
establish bidirectional RKRelationshipMappings
use mappings with nested mappings when sending JSON for both A and B objects. Ex. {"b": {"b_id":2, "a": {"a_id": 1}}}
Also, if you're working with the iOS simulator, be sure to set your HTTP response headers to not cache things. This nailed me a bunch of times since my code was using stale data.
Let's suppose I have a Core Data model using AFIncrementalStore, and I have multiple REST API endpoints for retrieving a list of objects of that model. I can override -requestForFetchRequest:withContext: in AFHTTPClient like so:
- (NSURLRequest *)requestForFetchRequest:(NSFetchRequest *)fetchRequest
withContext:(NSManagedObjectContext *)context {
NSMutableURLRequest *mutableURLRequest = nil;
if ([fetchRequest.entityName isEqualToString:#"Post"]) {
mutableURLRequest = [self requestWithMethod:#"GET" path:#"/posts/foo" parameters:nil];
}
return mutableURLRequest;
}
In this snippet, I retrieve Post objects at /posts/foo, but I also need to retrieve another set from /posts/bar.
How can I do this? The only solution I see is to make two models: one for foo and one for bar, but repeating yourself is lame, and there may be many more API endpoints that get Post objects for me that I'll need to support. Is there some other approach that I'm missing?
You need to inspect fetchRequest more closely than just looking at entityName. You can also look at fetchRequest.propertiesToFetch or possibly other things depending on your data model. You'll still need to send two requests, so just make sure your AFNetworking subclass can tell the difference.
Also: it sounds like your requestForFetchRequest:withContext: method might get really big. You might want to consider a more generic pattern in which you get your NSManagedObject subclass, and ask that to return a fetch request.
I have two objects, item and category. I have concrete urls for most queries so the mapping is fine. But for certain item request I have dynamic mapping that changes URLS and it looks like this:
[RKResponseDescriptor responseDescriptorWithMapping:goodMapping
pathPattern:nil
keyPath:nil
statusCodes:statusCodes]
If this is present, my Categories fail to map, although this is item mapping. If I comment this line out everything works fine.
My problem is that I can't set pathPattern, because even if I set the path to actual path it fails to detect it, here is the error I get:
failed to match: response path 'catalog/item?category_id=2' did not match the path pattern 'catalog/item?category_id=2'
I tried replacing "2" with ":id" to no avail. I can't set keyPath either, because I am getting unnamed array from server.
How do I change my mapping so it mapped my objects and detected them?
I managed to make it work by exactly matching pathPattern to request url, ignoring all get parameters, so it was "catalog/item" in request and "catalog/item" in mapping and everything worked, even if the resulting url is catalog/item?category_id=2.