RestKit: How to POST a NSManagedObject as JSON without any nesting attributes? - ios

This should be a really easy one, but sadly I haven't found any answer...
What RestKit mapped my object to:
request.body={"user":{"pass":"1234","id":0,"login":"awesome_guy","tier":0}}
What I really want:
{"pass":"1234","id":0,"login":"awesome_guy","tier":0}
Just without the object name "user".
If you have dealt with this issue, it'll take you 5 seconds to answer. If you haven't used RestKit. You do not know the answer. I'm attaching my code anyway:
User Object Mapping:
/* ===========================
* ====User Object Mapping====
* ==========================*/
RKEntityMapping* userObjectMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([User class]) inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *userObjectMappingDict = #{
#"id":#"id",
#"login":#"login",
#"firstName":#"firstName",
#"lastName":#"lastName",
#"phoneNumber":#"phoneNumber",
#"email":#"email",
#"tier":#"tier",
#"sessionId":#"sessionId",
#"pass":#"password"
};
userObjectMapping.identificationAttributes = #[#"id"];
[userObjectMapping addAttributeMappingsFromDictionary:userObjectMappingDict];
RKEntityMapping* userBusinessMapping = [RKEntityMapping mappingForEntityForName:#"Business" inManagedObjectStore:objectManager.managedObjectStore];
[userBusinessMapping addAttributeMappingsFromDictionary:#{#"business":#"id"}]; // Nil Key path
[userObjectMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:#"business" withMapping:userBusinessMapping]];

You should have an instance of RKRequestDescriptor that you haven't shown. It's created with requestDescriptorWithMapping:objectClass:rootKeyPath:. You have the root key path set to #"user" and you should set it to nil.

Related

How to Rectify the Relationship Fault in CoreData While parsing RESTKIT?

I want to Store the EY_ConnectionUsage Entity value to the EY_Connection entity Cell.I have Added two Entities with Attributes and Created Relationship with name usageData.But this Shows error "<'usageData' Relationship Fault>".This is a Method I wrote to map the RESTKIT value to CoreData.
DataAccessHandler.m
+(void)createConnectionMappingWithStore:(RKManagedObjectStore *)managedObjectStore saveInDelegate:(AppDelegate *)appDelegate{
NSLog(#"appdele==>>%#",appDelegate);
RKEntityMapping *connectionMapping = [RKEntityMapping mappingForEntityForName:#"EY_Connections" inManagedObjectStore:managedObjectStore];
connectionMapping.identificationAttributes = #[#"connectionNumber"];
[connectionMapping addAttributeMappingsFromDictionary:#{ #"ServiceNo" : #"connectionServiceNumber", #"Name" : #"connectionName", #"Region" : #"connectionRegion", #"Phase" : #"connectionPhase",
#"Circle" : #"connectionCircle", #"Section" : #"connectionSection", #"Load" : #"connectionLoad",
#"Distribution" : #"connectionDistribution", #"MeterNo" : #"connectionMeterNumber", #"ConnectionNumber" : #"connectionNumber", #"Address" : #"connectionAddress", #"ServiceStatus" : #"connectionStatus"}];
RKEntityMapping *connectionUsageMapping = [RKEntityMapping mappingForEntityForName:#"EY_ConnectionUsage" inManagedObjectStore:managedObjectStore];
connectionUsageMapping.identificationAttributes = #[#"usageAssessmentDate"];
[connectionUsageMapping addAttributeMappingsFromDictionary:#{ #"assessment_date" : #"usageAssessmentDate", #"reading" : #"usageReading", #"units" : #"usageUnits", #"amount" : #"usageAmount",
#"payment_date" : #"usagePaymentDate", #"status" : #"usageStatus"}];
[connectionMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"usage" toKeyPath:#"usageData" withMapping:connectionUsageMapping]];
RKResponseDescriptor *articleListResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:connectionMapping
method:RKRequestMethodGET
pathPattern:#"user_history/consumer/data.json"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
];
[appDelegate createObjectManagerForurl:#"http://sciflare.com/energyly/api/" andAddResponseDescriptor:articleListResponseDescriptor];
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
}
<'usageData' Relationship Fault>
This means that the relationship data hasn't been loaded yet, because you haven't tried to use it. Logging the object isn't enough to load the data. The whole point of the faulting system is to prevent too much data being loaded into memory at the same time.
So, basically, it isn't a problem that it's a fault. When you try to use it the data will be populated and everything will be fine.
CoreData says that 'load only needed data'. If we are trying to fetch unwanted data then coredata shows 'FAULT'.
Foe more detail please go through CoreData Fault

Using Keys as Values in RestKit

So, we have an API that spits out:
{
"2013-12-13": [subobject1, subobject2, subobjects3],
"2013-12-14": [subobject4, subobject5],
...
}
I'm not sure, though, how to parse things in RestKit where the key changes dynamically.
I'd probably like to parse it into a list of objects that have a date, and the list of subobjects.
Any thoughts?
You need to create a mapping something like:
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[XXX class]];
[mapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"date"];
[mapping addAttributeMappingsFromDictionary:#{
#"(date)": #"values"
}];
Where your XXX class has properties for date (NSDate) and values (NSArray).

Restkit dynamic mapping based on previous value

I'm dealing with the following json:
{
"status":
{
"errorCode":{errCode},
"errorMsg":{errMsg},
},
"data":
{
"key1":"value1",
"key2":"value2",
"key3":"value3",
}
}
I need to use different mapping for the object in data, according to errCode value. I tried to use RKDynamicMapping, but got confused with the keyPaths..
Is there any way to achieve that?
Edit:
I'm using this code:
RKObjectMapping *infoMapping = [RKObjectMapping mappingForClass:[Info class]];
[infoMapping addAttributeMappingsFromDictionary:#{#"data.key1":#"key1", #"data.key2":#"key2", #"data.key3:#"key3}];
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
[dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:#"status.errorCode" expectedValue:0 objectMapping:infoMapping]];
[dynamicActivateTravelMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
NSNumber *errorCode = [[representation valueForKey:#"status"] valueForKey:#"errorCode"];
if ([errorCode integerValue] == WSErrorUnknown) {
return unknownMapping;
}
else{
return infoMapping;
}
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping method:RKRequestMethodPOST pathPattern:kResource keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
Well, basically this code works, but I have couple of issues with it:
It seems very ugly to keep writing "data.x" for each attribute of the mapping.
The result dictionary comes back with NSNull as the key for the "data" mapping. (its value is fine though..)
status = "<ServerStatusCode: 0x155b2fa0>";
"<null>" = "Info: 0x15535800>";
Technically when you create the dynamic mapping it should be:
[dynamicMapping addMatcher:[RKObjectMappingMatcher matcherWithKeyPath:#"status.errorCode" expectedValue:#0 objectMapping:infoMapping]];
Note the #0, because you should be passing an object (NSNumber), not a plain number (where 0 will equate to nil and another number will cause problems).
The other 2 complaints you have are just the way things are. The first is because you need to index into the data. Both are caused by the keyPath:nil, specifying that the data is accessed from the top level and that there is no key with which to access the result.

Mapping relationships in RestKit through an array of IDs doesn't work

I'm trying to map users and groups to CoreData objects through RestKit, maintaining the relationship between the two.
The JSON for the users is something like
{"users":
[{"fullname": "John Doe", "_id": "1"}
...
]
}
and the JSON for the groups is something like
{"groups":
[{"name": "John's group", "_id": "a",
"members": ["1", "2"]
...
]
}
I also have corresponding Core Data objects, User and Group, with a one-to-many relationship mapping between groups and users. The relationship property in Group is called members and a separate NSArray called memberIDs holds the array of string IDs of all member users.
What I want to accomplish in RestKit is to load these objects and have the relationship mapped for me. The code for loading the users is straight forward standard stuff, and the code for loading the groups is something like
RKObjectManager* objectManager = [RKObjectManager sharedManager];
RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore];
groupMapping.primaryKeyAttribute = #"identifier";
[groupMapping mapKeyPath:#"_id" toAttribute:#"identifier"];
[groupMapping mapKeyPath:#"name" toAttribute:#"name"];
[groupMapping mapKeyPath:#"members" toAttribute:#"memberIDs"];
[objectManager.mappingProvider setMapping:groupMapping forKeyPath:#"groups"];
// Create an empty user mapping to handle the relationship mapping
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class] inManagedObjectStore:objectManager.objectStore];
[groupMapping hasMany:#"members" withMapping:userMapping];
[groupMapping connectRelationship:#"members" withObjectForPrimaryKeyAttribute:#"memberIDs"];
RKObjectRouter* router = objectManager.router;
[router routeClass:[Group class] toResourcePath:#"/rest/groups/:identifier"];
[router routeClass:[Group class] toResourcePath:#"/rest/groups/" forMethod:RKRequestMethodPOST];
// Assume url is properly defined to point to the right path...
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
When I run this code, I get the following warning spit out several times (seems to be about twice per relationship):
2012-10-24 08:55:10.170 Test[35023:18503] W restkit.core_data.cache:RKEntityByAttributeCache.m:205 Unable to add object with nil value for attribute 'identifier': <User: 0x86f7fc0> (entity: User; id: 0x8652400 <x-coredata:///User/t01A9EDBD-A523-4806-AFC2-9B06873D764E531> ; data: {
fullname = nil;
identifier = nil;
})
The strange thing is that the relationship gets set up properly (even looked at the sqlite database, and everything looks fine there) but I'm not happy with something clearly going wrong in the RestKit code, and it seems to have something to do with the bogus User mapping I create in the code above (it's empty, since there is nothing to map in the array of IDs).
I've tried alternatives, for example when I add a key path mapping:
[userMapping mapKeyPath:#"" toAttribute:#"identifier"];
It complains, obviously because the key path is empty
2012-10-24 09:24:11.735 Test[35399:14003] !! Uncaught exception !!
[<__NSCFString 0xa57f460> valueForUndefinedKey:]: this class is not key value coding-compliant for the key .
And if I try to use the real User mapping
[groupMapping hasMany:#"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:#"users"]];
I get the following error
2012-10-24 09:52:49.973 Test[35799:18403] !! Uncaught exception !!
[<__NSCFString 0xa56e9a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id.
2012-10-24 09:52:49.973 Test[35799:18403] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0xa56e9a0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key _id.'
Seems like RestKit is trying to map the ID strings themselves in the memberIDs array to User objects, which doesn't make any sense. I've seen examples where the relationship array is a list of dictionaries with keys (e.g. called id) with the reference ID to the other object, for example:
{"groups":
[{"name": "John's group", "_id": "a",
"members": [
{"_id": "1"},
{"_id": "2"}
]
...
]
}
But I don't want to change my JSON in that way (besides, I'm hoping RestKit is flexible enough to support the other way.)
Does anyone know what the proper way of doing this kind of relationship mapping in RestKit is?
Update
I ended up modifying the REST interface to send a list of dictionaries containing the user object IDs (just like the last example above), and got that to work. Still not completely happy with the setup, but the code now looks like
RKObjectManager* objectManager = [RKObjectManager sharedManager];
RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore];
groupMapping.primaryKeyAttribute = #"identifier";
[groupMapping mapKeyPath:#"_id" toAttribute:#"identifier"];
[groupMapping mapKeyPath:#"name" toAttribute:#"name"];
[groupMapping mapKeyPath:#"members._id" toAttribute:#"memberIDs"];
[objectManager.mappingProvider setMapping:groupMapping forKeyPath:#"groups"];
// Create an empty user mapping to handle the relationship mapping
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class] inManagedObjectStore:objectManager.objectStore];
userMapping.primaryKeyAttribute = #"identifier";
[userMapping mapKeyPath:#"_id" toAttribute:#"identifier"];
[groupMapping hasMany:#"members" withMapping:userMapping];
[groupMapping connectRelationship:#"members" withObjectForPrimaryKeyAttribute:#"memberIDs"];
RKObjectRouter* router = objectManager.router;
[router routeClass:[Group class] toResourcePath:#"/rest/groups/:identifier"];
[router routeClass:[Group class] toResourcePath:#"/rest/groups/" forMethod:RKRequestMethodPOST];
// Assume url is properly defined to point to the right path...
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
Just in case this helps anyone else in a similar situation. There might still be some problems with the approach I've taken, but so far so good (it even handles out of order loading, which is nice) - would still love to hear from anyone if there's an answer to my original question.
Found a way to make this work, and also realized that the updated approach in my question is only supposed to be used for nested objects, not referenced objects. This thread got me on the right track: https://groups.google.com/forum/#!msg/restkit/swB1Akv2lTE/mnP2OMSqElwJ (worth reading if you're dealing with relationships in RestKit)
Assume the groups JSON still looks like the original JSON:
{"groups":
[{"name": "John's group", "_id": "a",
"members": ["1", "2"]
...
]
}
Also assume that users are mapped to a keypath users (i.e. as in something like [objectManager.mappingProvider setMapping:userMapping forKeyPath:#"users"];) the correct way to map the relationships looks like this:
RKObjectManager* objectManager = [RKObjectManager sharedManager];
RKManagedObjectMapping* groupMapping = [RKManagedObjectMapping mappingForClass:[Group class] inManagedObjectStore:objectManager.objectStore];
groupMapping.primaryKeyAttribute = #"identifier";
[groupMapping mapKeyPath:#"_id" toAttribute:#"identifier"];
[groupMapping mapKeyPath:#"name" toAttribute:#"name"];
[groupMapping mapKeyPath:#"members" toAttribute:#"memberIDs"];
[objectManager.mappingProvider setMapping:groupMapping forKeyPath:#"groups"];
[groupMapping mapKeyPath:#"users" toRelationship:#"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:#"users"] serialize:NO];
[groupMapping connectRelationship:#"members" withObjectForPrimaryKeyAttribute:#"memberIDs"];
RKObjectRouter* router = objectManager.router;
[router routeClass:[Group class] toResourcePath:#"/rest/groups/:identifier"];
[router routeClass:[Group class] toResourcePath:#"/rest/groups/" forMethod:RKRequestMethodPOST];
// Assume url is properly defined to point to the right path...
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:url usingBlock:^(RKObjectLoader *loader) {}];
When I had tried this approach earlier, the thing that confused me was this line
[groupMapping mapKeyPath:#"users" toRelationship:#"members" withMapping:[[RKObjectManager sharedManager].mappingProvider objectMappingForKeyPath:#"users"] serialize:NO];
where the first key path is referring to the key path mapping for the objects being referenced (users in this case), not the key path of the relationship within the group object being mapped (which would have been members in this case).
With this change, all relationships work as expected and I'm happy.

RestKit primary key attribute

I load data from a json file, I save it.
I do it twice ...
I got two entries in my Core Data sqlite database.
Even if I set in the mapping the primaryKeyAttribute.
mapping.primaryKeyAttribute = #"code";
[mapping mapAttributesFromArray :mappedFields];
[[RKObjectManager sharedManager].mappingProvider setMapping:mapping forKeyPath:entityName];
My Json
{ "MyEntity": [ { "code" : "axv2","data" : "content"}]};
Here the callback :
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
NSLog(#"Entries loaded %d",[objects count]);
lastResult = objects;
for(MyEntity * myEntity in lastResult) {
[self saveContext];
}
}
My entity is correctly mapped ... But Restkit allow one to save duplicate entries with the same primary key?
It's weird, I understood that this primary key attribute would avoid this problem.
No, that is not the case, as Core Data keeps its own keys. You can easily solve this problem by checking if your primary key exists and before saving the entity instance in question.
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.

Resources