I'm running into an issue while trying to synchronize a list of favourited teams for a given user between my iOS app and my server. The flow of events is as follows:
User favourites a team
New favouriteTeam object is created and saved to Core Data:
NSError *error;
[[self.currentUser managedObjectContext] save:&error];
[[RKManagedObjectStore defaultStore].persistentStoreManagedObjectContext save:&error];
Array of modified favouriteTeams is POSTed to the server where they are timestamped and returned with any other modified or recently added (by another device) objects.
The problem I'm running into is that the item that is POSTed, since it is always returned is being duplicated instead of being overwritten (based on the identificationAttributes). None of the other objects returned (whether modified or newly created by another device) get duplicated... just the newly created device from the user's device.
Here's my Request/Response mapping code:
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager setRequestSerializationMIMEType:RKMIMETypeJSON];
// POST Request Mapping
RKObjectMapping *favouriteTeamMapping = [RKObjectMapping requestMapping];
[favouriteTeamMapping addAttributeMappingsFromArray:#[#"uuid", #"teamName", #"displayOrder"]];
RKRequestDescriptor *favouriteTeamRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:favouriteTeamMapping objectClass:[FavouriteTeam class] rootKeyPath:#"favouriteTeams"];
[objectManager addRequestDescriptor:favouriteTeamRequestDescriptor];
// Response Mapping
RKEntityMapping *favouriteTeamResponseMapping = [RKEntityMapping mappingForEntityForName:#"FavouriteTeam" inManagedObjectStore:objectManager.managedObjectStore];
favouriteTeamResponseMapping.identificationAttributes = #[#"uuid"];
[favouriteTeamResponseMapping addAttributeMappingsFromArray:#[#"uuid", #"teamName", #"displayOrder", #"lastModified"]];
RKResponseDescriptor *favouriteTeamResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:favouriteTeamResponseMapping pathPattern:#"/api/favouriteteam/" keyPath:#"data.favouriteTeams" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptorsFromArray:#[favouriteTeamResponseDescriptor]];
// POST
[objectManager postObject:favTeamsArray path:#"/api/favouriteteam/" parameters:nil success:nil failure:nil];
I can't seem to figure out why this duplication is happening when the identificationAttributes (favouriteDrug "uuid" attribute) are set. The objects are identical (even the same uuid) in Core Data. What is causing this behaviour?
NOTE: This question is also posted on the RestKit Google Groups here.
Thanks!
UPDATE: It appears that this duplication doesn't occur the first time a team is favorited. Any of the following attempts to favorite a team result in this duplication.
I had a similar issue, and while I cannot really confirm if this addressed your problem, will post my answer here anyways.
My problem was that the first object would be created fine, but thereafter restkit would save duplicate objects in coredata, so this sounds like the problem you are seeing.
I was making the post call to create object by:
* creating a blank object in coredata and filling it with whatever attributes I need
* make a post call to server using restkit API and passing in the newly created object
What restkit does under the hood is to take the response, fill it into the new object I created, and save it into coredata WITHOUT checking if there is another object of the same unique id beforehand. This is why the first object creation was fine, but subsequent objects were duplicates.
The way I solved it was actually to pass in the raw values as params to the restkit post API call, and nil as the object. On the reply, restkit will then look through coredata first to see if an object of that ID exists and merge changes with that object, or create a new one.
Related
I am using RestKit with Core Data and I am having trouble mapping the relationships between entities. The issue is that I have 3 different jsons (for 3 different screens) and these jsons have interrelated data. My 3 entities are Post, Conversation, and Message. The relationships are as such: Each post has many conversations (to-many relationship) and each conversation has many messages (to-many relationship).
Here is the first json for an array of posts:
{"success":true,"result":
[{"totalUsers":1,"lastMessageDate":1411612821000,"id":874,"title":"My post title"},
{"totalUsers":3,"lastMessageDate":1411536669000,"id":539,"title":"Message me"}]}
Here is the second json, which is an array of conversations for a specific post:
{"success":true,"result":
[{"badgeSize":1,"lastMessage":
{"id":1725,"text":"hey","datePublished":141153372347},"id":208,"username":"energydrink"}]}
Here is the third json for a particular conversation:
{"success":true,"result":
{"spamStatus":false,"messages":
[{"id":416,"text":"hello","datePublished":1403432789000},
{"id":380,"text":"whats up","datePublished":1403432144221}]}}
So as you can see, the data for the relationships is split up into 3 jsons. Here is how I have currently configured my relationships and response descriptor within RestKit:
[conversationMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"messages" toKeyPath:#"messages" withMapping:messageMapping]];
[postMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"conversations" toKeyPath:#"conversations" withMapping:messageMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:postMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"result" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
Since the response descriptor is mapped with postMapping, I am unable to correctly map data for the 2nd and 3rd jsons and then store within Core Data with my configuration. Any help is greatly appreciated.
I figured it out. Basically, I used RestKit's mapping for the first json. When tableView:didSelectRowAtIndexPath: was called on the first view controller, I passed the post object to the second view controller. For the second json response, I deserialized the data and added the conversations to that post object. Then, I simply called [[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext saveToPersistentStore:&error];. Now it works just fine.
I've got an iOS app that uses RestKit with Core Data persistence. It loads its data from a few different endpoints, most of which return complete object graphs as nested JSON objects. But one of the endpoints returns objects that contain foreign key references (not nested JSON) to another endpoint's data.
In other words (abbreviated):
[postMapping addAttributeMappingsFromArray:#[ #"postID", ... ]];
// ...
[commentMapping addAttributeMappingsFromArray:#[ ... ]];
[commentMapping addConnectionForRelationship:#"post" connectedBy:#"postID"];
where the relevant Post and Comment entities are set up with the appropriate relationships and so forth.
This all works exactly as it should but only if the request to fetch posts finishes before the request to fetch comments. Otherwise a comment won't be connected to its post, because the post hasn't been fetched and mapped yet.
Right now, I'm getting around it by just not fetching the comments until the posts have been fetched. Something like
- (void)fetchPosts {
[objectManager getObjectsAtPath:#"/posts.json"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self fetchComments]; // *now* we can fetch comments
// ...
But I'm wondering if there's a better way?
I figure I might be able to do something like
[objectManager.HTTPClient.operationQueue setMaxConcurrentOperationCount:1];
but most of the time, I do want concurrent requests. It's only two of the requests that must be sequential.
I guess I could set up two different object managers (one that does sequential requests and one that does concurrent ones), but I'm not sure that makes sense.
So, is there are way to specify that two specific -getObjectsAtPath:... requests should be handled sequentially, while all other can run whenever?
Look at creating another request descriptor which drills down into the comment details and processes only the postID attributes. For each one, run the postMapping to create a stub object.
Note that you might still run into race conditions with 2 different background contexts both creating the same objects at the same time, so you may need to handle merge issues (this is if you have multiple object managers as you talked about multiple endpoints).
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.
I have a core data mapped object managed by Restkit and am able to fetch JSON from my web service using
[[RKObjectManager sharedManager] getObjectsAtPath:jobRequest parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
which works beautifully. If I request the same object more than once, it isn't creating a new managed object if it exists in the RKInMemoryManagedObjectCache.
My question though, is how can I tell if the object was already in the cache? I would like to display an 'up to date' style message to the user if they already have the item - I can query this manually against my core objects but is this something that is exposed by RestKit (perhaps in the RKMappingResult parameter of the success block)?
You would use Core Data to determine what's new. You can observe changes to the managed object context and it will give you sets of inserted, updated and deleted objects.
I'm working with an API which allows updating of objects with PUT requests - with the following format (w/ curl):
curl --request PUT -u <api_key>: <api_url> \
-d "attribute=newvalue"
i.e. any attributes of the associated object (referenced in the API url) which are listed in the curl are then updated, and the updated object is returned.
I'm using RestKit with mapped objects, and everything works dandy with GET. But after updating attributes in the mapped objects, I'm struggling to get the putObject method in RKObjectManager to work, e.g.:
[objectManager putObject:<someObject>
path:path
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
[self.refreshControl endRefreshing];
}
];
Whatever I put in as the first parameter - an updated mapped object, a JSONified dictionary, even just a plain old body string, seems to return a "Could not parse request data, invalid JSON" error from RestKit. What am I doing wrong?
Your <someObject> should be an instance of a class from your data model. The class must have a defined set of mappings.
Say you had a class Person. You define a mapping and a response descriptor so RestKit can convert downloaded JSON into Person objects. Now, you need to obtain the inverseMapping to define your request descriptor so RestKit knows how to serialise for upload.
This allows RestKit to take your <someObject>, serialise it into JSON (request descriptor), send it to your server and then receive a response and map that data back into updated objects (response descriptor).
Check the example here. It uses a dictionary to hold the updated attributes but the principle is the same. Using the dictionary just makes it easier to upload partial objects instead of full objects.