I have an iOS app that uses RestKit to sync between my Core Data model and a Rails API.
I have Game and Team entities in my Core Data model. A Game has a to-many relationship to Teams. I am trying to update the 'score' attribute of the Teams, and then I am trying to run the putObject method on my RKObjectManager by sending in the Game. The scores of the teams are not updating on the server.
If I change an attribute of the Game, e.g. the 'state', and then send in the Game with putObject, it works correctly.
Is it possible to update more than one object with putObject given that the object has nested objects inside of it? Or do I need to run putObject on the Team when I update its 'score' attribute?
Here is my mapping code for Games.
Class itemClass = [Game class];
RKEntityMapping *mapping = [RKEntityMapping
mappingForEntityForName:#"Game"
inManagedObjectStore:managedObjectStore];
mapping.identificationAttributes = #[#"gameID"];
NSDictionary *standardDict = #{#"id": #"gameID",
#"created_at": #"createdAt",
#"updated_at": #"updatedAt"};
NSDictionary *gameDict = #{#"league_id": #"leagueID",
#"location_id": #"locationID",
#"state": #"state",
//.... more attributes....
};
[mapping addAttributeMappingsFromDictionary:standardDict];
[mapping addAttributeMappingsFromDictionary:gameDict];
[mapping addConnectionForRelationship:#"league" connectedBy:#{#"leagueID": #"leagueID"}];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationID": #"locationID"}];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
NSString *keyPath = nil;
NSString *itemsPath = #"games/:gameID";
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodAny
pathPattern:itemsPath
keyPath:keyPath
statusCodes:statusCodes];
[manager addResponseDescriptor:responseDescriptor];
NSString *itemPath = #"game";
RKEntityMapping *requestMapping = [mapping inverseMapping];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:itemClass
rootKeyPath:itemPath
method:RKRequestMethodAny];
[manager addRequestDescriptor:requestDescriptor];
//route for manipulating with existing object
RKRoute *itemRoute = [RKRoute routeWithClass:itemClass pathPattern:#"games/:gameID" method:RKRequestMethodAny];
itemRoute.shouldEscapePath = YES;
[manager.router.routeSet addRoutes:#[itemRoute]];
The mapping for a Team is written basically the exact same way, except a Team has a connection to a Game based on the Game's 'gameID.' So --> [mapping addConnectionForRelationship:#"game" connectedBy:#{#"gameID": #"gameID"}];
You are using foreign key mappings on your response mapping:
[mapping addConnectionForRelationship:#"league" connectedBy:#{#"leagueID": #"leagueID"}];
[mapping addConnectionForRelationship:#"location" connectedBy:#{#"locationID": #"locationID"}];
and these are not reversed when you use inverseMapping (because they don't contain enough information to create the inverse).
So, your requestMapping needs to be explicitly updated to include a relationship mapping to tell RestKit to process the relationship and how to map the relationship contents into the resulting JSON.
Related
I am creating a response descriptor for json to core data mapping in RestkitManager. The parent object is "level" and it has an array of "sublevel" objects.
RKDynamicMapping *levelMapping = [Level map];
RKResponseDescriptor* levelRd = [RKResponseDescriptor responseDescriptorWithMapping:levelMapping method:RKRequestMethodGET pathPattern:#"entity/:entityId" keyPath:#"summary.levels" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addResponseDescriptor:levelRd];
In Level class
+ (RKEntityMapping *)mapping {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"name" : #"name",
#"state" : #"state"
}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"sublevel" toKeyPath:#"sublevelList" withMapping:[Sublevel map]]];
return mapping;
}
In Sublevel
+ (RKEntityMapping *)map {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"staticNode.obj.name" : #"name"
}];
return mapping;
}
When I try to fetch sublevel on object level, I get it in random order. Sometimes 2nd sublevel get printed first. Is there any way to maintain the order?
As I understand, when mapping is done, I don't have any control over what is getting persisted in the database. Hence, I am not able to assign any order number myself. Apart from that, I have explored metadata.routing.parameters but for this I need to pass parameters in the API call itself - which is not desirable.
Any pointers on how to maintain the order would be helpful.
How cant I set a static value while mapping entities?
I have a JSON response like this:
"friends": [
{
"id": 123,
"name": "Friend",
},
]
"featured": [
{
"id": 456,
"name": "Some Featured user",
},
]
My mapping and descriptors look like this:
RKMapping *friendsMapping = [ProjectMappingProvider userMapping];
RKMapping *featuredMapping = [ProjectMappingProvider featuredUserMapping];
RKResponseDescriptor *friendsResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:friendsMapping
method:RKRequestMethodGET
pathPattern:#"/api/users"
keyPath:#"friends"
statusCodes:statusCodeSet];
RKResponseDescriptor *featuredResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:friendsMapping
method:RKRequestMethodGET
pathPattern:#"/api/users"
keyPath:#"featured"
statusCodes:statusCodeSet];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request
responseDescriptors:#[
friendsResponseDescriptor,
featuredResponseDescriptor]];
... some code emited for readabilty ...
Now mu friendsResponseDescriptor and featuredResponseDescriptors look almost identical, but I would like to set additional CoreData parameter accordingly. Objects mapped through friendsDescriptor should have section = 0 and objects mapped through featured descriptor should have section = 10.
So, can I do something like this?
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"User"
inManagedObjectStore:[[DataModel sharedDataModel] objectStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"userId",
#"name": #"name" }];
mapping.identificationAttributes = #[ #"userId" ];
// How can I do somethning like this?
[mapping setValue:#0 forKey:#"section"];
And the featured mapping:
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"User"
inManagedObjectStore:[[DataModel sharedDataModel] objectStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"userId",
#"name": #"name" }];
mapping.identificationAttributes = #[ #"userId" ];
// How can I do somethning like this?
[mapping setValue:#10 forKey:#"section"];
Note that I don't have any other indicator whetever user is a friend or featured in the user JSON itself. The only way I can distinguish the type of user (friend,featured) is in which list in JSON response the user is set.
I am later using the section property in the table view controller to have sections.
If you are using different entities, set default values on them. If you aren't using different entities, consider changing so that you are (they could be sub-entities of a common parent).
You can't insert data into the mapping. The mapping only describes what RestKit should process. To edit the values you would need to get involved in the mapping process yourself and implement some delegate methods to inject additional data.
I'm stuck on the question of how to build up my objects and mapping to achieve something like this when putting data via the PUT-Method: "lastChanges/confirm"
The above PUT-Request accepts a body like this to confirm synchronization of box ids:
{ "synchronized_boxes": [47292,someOtherBoxId,..] }
I have tried building an Object like this:
#interface RPConfirmSync : NSObject
#property (nonatomic, retain) NSArray *synchronized_boxes;
#end
Before I send this Object I add some NSNumber Objects to the array.
The mapping I set up looks like this:
RKObjectMapping *confirmMapping = [RKObjectMapping mappingForClass:[RPConfirmSync class]];
[confirmMapping addAttributeMappingsFromArray:#[#"synchronized_boxes"]];
RKObjectMapping *requestMapping = [confirmMapping inverseMapping];
NSString *pathPattern = [NSString stringWithFormat:#"lastsync/confirm"];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[RPConfirmSync class] rootKeyPath:nil];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:confirmMapping pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[self.objectManager addRequestDescriptor:requestDescriptor];
[self.objectManager addResponseDescriptor:responseDescriptor];
Now, when I execute the above PUT-Request and look at the request body, the RestKit Debug information shows me something weird like this:
request.body=synchronized_boxes[]=47292 //being sent to the server !ERROR!
which should be
request.body=synchronized_boxes[47292]
How do I have to set up my Object or is there something wrong with the mapping? I'm really stuck here, although I guess the answer is straight forward.
I got a problem with mapping a nested object value.
I got two objects with the following properties:
a)
class Input
#property NSString value;
#property NSString title;
b)
class Profile
#property Input myAwesomeInput;
..so a Profile contains an Input object. When I mapp the objects with RestKit (0.20) I get sth. like this:
{ myAwesomeInput_test:{"value":"xyz","title":"a title"}}
What I wanna achieve is:
{myAwesomeInput_test:"xyz"}
So I don't want to map "Input" but just the Input.value. Is that even possible?
At the moment my code looks like this:
RKObjectMapping* inputMapping = [RKObjectMapping requestMapping];
[inputMapping addAttributeMappingsFromArray:#[#"value"]];
RKRequestDescriptor *reqDescInput = [RKRequestDescriptor requestDescriptorWithMapping:inputMapping objectClass:[Input class] rootKeyPath:nil];
RKObjectMapping* searchProfile = [RKObjectMapping requestMapping];
RKRequestDescriptor *reqDescSearchProfile = [RKRequestDescriptor requestDescriptorWithMapping:searchProfile objectClass:[SearchProfile class] rootKeyPath:nil];
[searchProfile addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"myAwesomeInput" toKeyPath:#"myAwesomeInput_test" withMapping:inputMapping]];
EDIT: (solved)
Ok I solved it. Hope it's the way people should do it. You can directly address from within the dictionary.
RKObjectMapping* searchProfile = [RKObjectMapping requestMapping];
[aeSearchProfile addAttributeMappingsFromDictionary:#{
#"myAwesomeInput.value": #"myAwesomeInput_test"
}];
RKRequestDescriptor *reqDescSearchProfile = [RKRequestDescriptor requestDescriptorWithMapping:searchProfile objectClass:[SearchProfile class] rootKeyPath:nil];
Use keypaths rather than multiple mappings.
Try this:
RKObjectMapping* searchProfile = [RKObjectMapping requestMapping];
[searchProfile addAttributeMappingsFromDictionary:#{ #"myAwesomeInput.value" : #"myAwesomeInput_test" }];
RKRequestDescriptor *reqDescSearchProfile = [RKRequestDescriptor requestDescriptorWithMapping:searchProfile objectClass:[SearchProfile class] rootKeyPath:nil];
I have the following objects setup:
RKObjectMapping* geoPointMapping = [RKObjectMapping mappingForClass:[CRGeoPoint class]];
geoPointMapping.setDefaultValueForMissingAttributes = YES;
[geoPointMapping mapKeyPathsToAttributes:
#"longitude", #"longitude",
#"latitude", #"latitude",
nil];
[objectManager.mappingProvider registerMapping:geoPointMapping withRootKeyPath:#"geometry"];
RKObjectMapping* criteriaMapping = [RKObjectMapping mappingForClass:[CRCriteria class]];
criteriaMapping.setDefaultValueForMissingAttributes = YES;
[criteriaMapping mapKeyPathsToAttributes:
#"type", #"type",
#"geometry", #"geometry",
#"fromDate", #"fromDate",
#"toDate", #"toDate",
#"radius", #"radius",
nil];
[objectManager.mappingProvider registerMapping:criteriaMapping withRootKeyPath:#"criteria"];
But when I try and send the query (with a geometry object). I keep
getting this error:
error received Error Domain=JKErrorDomain Code=-1 "Unable to serialize
object class CRGeoPoint."
Inbound and outbound mapping information is handled separately by RKObjectMappingProvider. You've configured the inbound mapping in that code (downloading the data from your server). But--as pointed out by Shane Zatezalo on the RestKit group--you also need to add a serialization mapping for RestKit to know how to turn your objects into JSON (or whatever other serialization format you might be using).
Check out the Object Mapping tutorial's section on Object Serialization. And take a look at the source for the mapping provider to reassure yourself that these things are separate.
Solution by OP.
The solution was two-fold. First I had to specify the inverse mapping as Sixten pointed out below, second I had to specify the mapping to use for the CRGeoPoint class on the CRCriteria object. Code below:
RKObjectMapping* geoPointMapping = [RKObjectMapping mappingForClass:[CRGeoPoint class]];
geoPointMapping.setDefaultValueForMissingAttributes = YES;
[objectManager.mappingProvider registerMapping:geoPointMapping withRootKeyPath:#"geometry"];
// Build a serialization mapping by inverting our object mapping. Includes attributes and relationships
RKObjectMapping* geoPointSerializationMapping = [geoPointMapping inverseMapping];
// You can customize the mapping here as necessary -- adding/removing mappings
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:geoPointSerializationMapping forClass:[CRGeoPoint class]];
RKObjectMapping* criteriaMapping = [RKObjectMapping mappingForClass:[CRCriteria class]];
criteriaMapping.setDefaultValueForMissingAttributes = YES;
[criteriaMapping mapKeyPathsToAttributes:
#"type", #"type",
#"fromDate", #"fromDate",
#"toDate", #"toDate",
#"radius", #"radius",
nil];
[criteriaMapping mapKeyPath:#"geometry" toRelationship:#"geometry" withMapping:geoPointMapping];
[objectManager.mappingProvider registerMapping:criteriaMapping withRootKeyPath:#"criteria"];
// Build a serialization mapping by inverting our object mapping. Includes attributes and relationships
RKObjectMapping* criteriaSerializationMapping = [criteriaMapping inverseMapping];
// You can customize the mapping here as necessary -- adding/removing mappings
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:criteriaSerializationMapping forClass:[CRCriteria class]];