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.
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.
JSON
{
"entity": {
"id": 1,
"name": "name"
},
"settings": {
"key": "value"
}
}
Entity Model
Attributes: id, name
Relationship: settings(one to one)
Settings Model
Attributes: key
Relationship: entity(reverse relationship, one to one)
EntityMapping
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([self class]) inManagedObjectStore:[RKManagedObjectStore defaultStore]];
mapping.persistentStore = [RKManagedObjectStore defaultStore].persistentStoreCoordinator.persistentStores.firstObject;
mapping.identificationAttributes = #[#"id"];
[mapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"name" : #"name"
}];
Had settings been inside entity in json response, I would add this
[mapping addPropertyMapping:
[RKRelationshipMapping relationshipMappingFromKeyPath:#"settings"
toKeyPath:#"settings" withMapping:[Settings map]]
];
But my response id different than this, so how should I map correctly?
Both entity and settings are in a container dictionary, so you can change your response descriptor to not drill down so far and then use key paths to drill into the entity part and have direct access to the settings part:
[mapping addAttributeMappingsFromDictionary:#{
#"entity.id" : #"id",
#"entity.name" : #"name"
}];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"settings" toKeyPath:#"settings" withMapping:[Settings map]]];
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.
I have the following JSON:
"Menus":{
"Id" : "3",
"Name" : "Cheese Burger",
"Items": []
}
I map the response to Core Data like so:
+ (RKEntityMapping *)menuMapping {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Menu" inManagedObjectStore:[[CoreDataManager sharedInstance] objectStore]];
mapping.assignsNilForMissingRelationships = YES;
mapping.assignsDefaultValueForMissingAttributes = YES;
[mapping addAttributeMappingsFromDictionary:#{
#"Id": #"remoteID",
#"Name": #"name",
}];
mapping.identificationAttributes = #[ #"remoteID" ];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Items"
toKeyPath:#"products"
withMapping:[MappingProvider productMapping]]];
return mapping;
}
+ (RKEntityMapping *)productMapping {
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Product" inManagedObjectStore:[[CoreDataManager sharedInstance] objectStore]];
mapping.assignsNilForMissingRelationships = YES;
[mapping addAttributeMappingsFromDictionary:#{
#"Id": #"remoteID",
#"Name": #"name",
#"Description" : #"info",
#"Price": #"price"
}];
mapping.identificationAttributes = #[ #"remoteID" ];
return mapping;
}
How would I manage to validate if the Items array is empty or not, and create the Menu object in Core Data based on if the Items (the products) contains values? I have tried using the assignsNilForMissingRelationships but it does not seem to work in this case.
Use KVC validation to analyse the incoming value and (modify or) reject it.
I need to map JSON associative array of objects with RestKit(iOS).
It looks like object with properties 135,145,423 and objects on it.
{
"135": {
"name" : "Object1",
"property1" : "Value1",
"anotherProperty1" : "Value2"
},
"145": {
"name": "Object2",
"property1" : "Value1",
"anotherProperty1" : "Value2"
},
"423": {
"name": "Object3",
"property1" : "Value1",
"anotherProperty1" : "Value2"
}
}
I've got mapping for single object that works.
Mapping performs to CoreData.
The only solution i have is to convert associative array to ordinary array and place number to "id" field, but i don't think it's elegant solution.
Is there any right way to perform such mapping directly with RestKit?
Here's the solution for my situation.
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); // Anything in 2xx
// 1. Create dynamic mapping
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
// 2. Process every entry separately
dynamicMapping.forceCollectionMapping = YES;
// 3. Set mappings for every object
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
// 4. Mapping to Core Data (Can be replaced with RKObjectMapping if there's no need of CodeData)
RKEntityMapping *singleRouteMapping = [RKEntityMapping mappingForEntityForName:#"Object" inManagedObjectStore:managedObjectStore];
// 5. Walking through all keys (but with dynamicMapping.forceCollectionMapping = YES) there'll be only one. It's better to refactor it.
for (NSString *key in representation) {
// 6. Set mappings for every property exect 'id'
[singleRouteMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat: #"%#.name", key]: #"name",
[NSString stringWithFormat: #"%#.property1", key]: #"property1",
[NSString stringWithFormat: #"%#.anotherProperty1", key]: #"anotherProperty1"
}];
}
// 7. Map 'id' property at last
[singleRouteMapping addAttributeMappingFromKeyOfRepresentationToAttribute: #"id"];
return singleRouteMapping;
}];
RKResponseDescriptor *pluralDescriptor = [RKResponseDescriptor responseDescriptorWithMapping: dynamicMapping
pathPattern: #"/api/objects"
keyPath: nil
statusCodes: statusCodes];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.somesite.com/api/objects"]];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[pluralDescriptor]];
You need to use a dynamic mapping, where the mapping is created specifically for the received keys in the dictionary. You don't say what the destination object is or what your mappings are so this is a general example (for Core Data, but can be changed to plain objects):
RKDynamicMapping* dynamicMapping = [[RKDynamicMapping alloc] init];
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
RKEntityMapping* typeMapping = [RKEntityMapping mappingForEntityForName:#"..." inManagedObjectStore:objectStore];
for (NSString *key in representation) {
NSDictionary *type = [representation objectForKey:key];
[typeMapping addAttributeMappingsFromDictionary:#{
[NSString stringWithFormat:#"%#.name", key]: #"name"}];
}
return typeMapping;
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping
pathPattern:...
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
This basically strips out the numbers and throws them away. You could include them if required by configuring the dynamic mapping.