RestKit: Inverse Relationship Empty After Initial Mapping - ios

Trying to connect two entities from my API from a single response.
{
"id": 4546,
"body": "Direct message",
"status": "received",
"from": {
"id": 723,
"signature": "Mr. Whatever"
},
"sent_at": "2014-06-05T21:33:15Z",
"sent_to": ...
}
The object is a Message entity, the from field is a nested Sender entity. Here is the relevant relationship mapping:
RKEntityMapping *senderMapping = [RKEntityMapping mappingForEntityForName:#"Sender"
inManagedObjectStore:[AMModelManager sharedManager].managedObjectStore];
senderMapping.identificationAttributes = #[ #"remoteId" ];
[senderMapping addAttributeMappingsFromDictionary:#{
#"id" : #"remoteId",
#"signature" : #"signature"
}];
[messageMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"from"
toKeyPath:#"sender"
withMapping:senderMapping]];
The first time the objects are mapped, the relationship is not established. I see it being mapped in the log, but when I print any particular Sender object, its messages inverse relationship shows an empty array instead of a relationship fault.
Mapped relationship object from keyPath 'from' to 'sender'. Value: <Sender: 0x16a10990> (entity: Sender; id: 0x16a14580 <x-coredata:///Sender/t16539BDF-5606-442B-9EE2-D88363FB04AD29> ; data: {
messages = (
);
remoteId = 723;
signature = "Mr. Whatever";
})
This makes the fetch requests I have in my application fail when I want to search for all messages from a given sender.
The next time I run the app and the mapping takes place again, all Message and Sender entities are remapped, the relationship is connected as expected, and my fetch requests behave as expected.
I really have no idea why the relationship isn't valid on the initial load. I don't know if it's a Core Data config issue or a RestKit issue, but it's driving me crazy and I would love any additional insight available. Glad to provide more information if necessary.
Edit
This is different than the proposed duplicate because this is a to-one relationship, so the proposed answer of using RKAssignmentPolicyUnion is inapplicable.
Update
Here is the fetch request I'm using for the controller I'm listening to:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Sender"];
fetch.predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(SELF.messages, $message, $message.direct == %#).#count > 0 && SELF.remoteId != %#", #(YES), user.remoteId];
fetch.sortDescriptors = #[
[NSSortDescriptor sortDescriptorWithKey:#"signature" ascending:YES selector:#selector(caseInsensitiveCompare:)]
];
Semantically I'm trying to say "Give me all the Senders that have sent a Message directly to the current user, and who are not the current user, sorted by their signature."
This fetch request fetches the Sender objects that I expect when I run it after my data is retrieved, but the controller fires no updates and my table remains empty until the next time I run the application.

K, another classic case of "You did it wrong and you were looking in all the wrong places to fix it."
I'd overridden the didChangeValueForKey: method on my Message entity's implementation to monitor some transient state, and neglected to call [super didChangeValueForKey:key], which fubar'd the inverse connection assignments in the mapping. Absolutely no issue with Core Data or RestKit. Just me being careless.

Related

iOS Core Data <fault after searching

All, I am having problems with Core Data.
I have a method that queries all data which matches a jobId
- (JobSummary*)summaryForJobId:(NSInteger)jobId {
NSFetchRequest* request = [[NSFetchRequest alloc] initWithEntityName:[JobSummary entityName]];
request.predicate = [NSPredicate predicateWithFormat:#"jobId = %D", jobId];
JobSummary* summary = [[self.context executeFetchRequest:request error:nil] lastObject];
NSLog(#"DB Summary: %#", summary);
[request setReturnsObjectsAsFaults:NO];
return summary;
}
When called and I log out it works perfect, however when I call it from a seperate view controller like so;
JobSummary *retrievedDictionary = [[FSScheduleDatabaseTransaction new] summaryForJobId:jobid];
When I log out retrievedDictionary it spits out this;
<JobSummary: 0x12de24a0> (entity: JobSummary; id: 0xb3c19b0 <x-coredata://7E9F6C6E-B4A0-4450-8905-184C6C8FB60D/JobSummary/p169> ; data: <fault>)
Any help much appreciated!
It is giving you the log correctly. When ever you try to print the ManagedObject you'll only see a fault in logs.
A fault is a placeholder object that represents a managed object that has not yet been fully realized, or a collection object that represents a relationship:
A managed object fault is an instance of the appropriate class, but its persistent variables are not yet initialized.
A relationship fault is a subclass of the collection class that represents the relationship.
So, in short, unless you try to access those properties, CoreData wont fill property values. It will have a fault. Just trying to log the ManagedObject, without accessing its properties previously, will only show fault.

Core Data & RestKit: Multiple relation to same object

I have a problem with the object mapping from a JSON string to a NSManagedObject. I use RestKit via getObject request and the Core Data integration. And I want to automatically map the JSON response.
1.)
I get from a webservice the following response ("A" object):
{
"Bs":[
{ "id" : "abc", "name" : "name 1"},
{ "id" : "def", "name" : "name 2"}
{ "id" : "abc", "name" : "name 1"},
],
"id": "1"
}
2.)
This response has ordered values (B Objects), with sometimes the same object ("id" : "abc") more than one time. Further more, the order of the items is important.
3.)
Core-Data has not the support to save multiple relations to the same object, because it used a NSSet (NSOrderedSet). And it ignores all double objects.
Has anyone an idea how I could solve the problem?
My try, which fails:
1.)
I insert a new core data table (AB), which:
has a reference to A
has a position field
has a reference to one B, from the A
2.)
I try to map the object with a RKValueTransformer instance.
This iterates over the JSON response for the B instances and create AB objects with the current position. These objects are saved in an NSSet, which return from the custom value transformer
RKValueTransformer *aabbTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass) {
return ([sourceClass isSubclassOfClass:[NSArray class]] && [destinationClass isSubclassOfClass:[NSOrderedSet class]]);
} transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, Class outputValueClass, NSError *__autoreleasing *error) {
// Validate the input and output
RKValueTransformerTestInputValueIsKindOfClass(inputValue, [NSArray class], error);
RKValueTransformerTestOutputValueClassIsSubclassOfClass(outputValueClass, [NSOrderedSet class], error);
NSMutableOrderedSet *outputSet = [[NSMutableOrderedSet alloc] init];
NSInteger pos = 1;
for (id b in inputValue) {
// see JSON output at the top
// B instance already exists in core data persistent store
NSString *bid = [b valueForKeyPath:#"id"];
B *b = [B bById:bid];
// create AB instance
AB *ab = [NSEntityDescription ...]
ab.b = b;
ab.position = [NSNumber numberWithInteger:pos];
[outputSet addObject:ab];
pos++;
}
// return for A.abs
*outputValue = [[NSOrderedSet alloc] initWithOrderedSet:outputSet];
return YES;
}];
RKAttributeMapping *aabbMapping = [RKAttributeMapping attributeMappingFromKeyPath:#"bs" toKeyPath:#"abs"];
aabbMapping.valueTransformer = aabbMappingTransformer;
3.) But I get an error:
illegal attempt to establish a relationship 'abs' between objects in different contexts
But I use always the same context.
If you don't have an better idea, do you have a solution for this problem?
Your proposed model structure is a sensible solution for your duplicate data requirement. The only change you should make there is to ensure that all relationships are double ended (have an inverse) and have appropriate multiplicity. Be sure that the relationship from B to AB is to-many. The relationship from A to AB should also be to-many.
The general approach to this is to use multiple response descriptors:
One to create the A object and AB objects, key path is nil
One to create the B objects without any duplication, key path is Bs
The response descriptor to create the A object has a nested relationship mapping to create the AB objects with duplicates and order - for this you can not use any identification attributes for AB and you need to use the mapping metadata provided by RestKit.
Once the objects are created they then need to be connected and that would be done with a foreign key mapping. That means storing a transient attribute on the AB instances which contains the id for the appropriate B object and using that to make the connection.
Note that after updates you may have some orphaned objects (because you can't use identification attributes for AB) in the data store as a result of this manipulation and you should consider creating a fetch request block to purge them out (or do this yourself periodically).

RestKit: Ignoring some dynamic nesting attributes

I receive JSON objects like this:
{
"rent": {
"id": "someId"
},
"upcoming": {
"id": "someId"
},
"watchnow": {
"id": "someId"
}
}
I then set forceCollectionMapping to YES on my mapping to get one object for each key, i.e. one object for "rent", one for "upcoming" and one for "watchnow". Specifically this is done with this code:
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So this succesfully gives me three objects for which I can then do some relationship mapping to get the id keys and what ever else is on the object.
Now, my problem is that if an error occurs, I get this JSON code:
{
"error": {
"errorcode": "someId"
}
}
So (searchSection) becomes "error" and my relationship mapping looks for "id" but it's not there so the mapping fails. The problem is that setting addAttributeMappingFromKeyOfRepresentationToAttribute makes RestKit try to make an object from every single key, and I can't expect every key to be relevant and useful for my mappings. Can I do anything about this?
You have a couple of options:
Use an RKDynamicMapping as the root mapping for your response descriptor
Use multiple response descriptors to specify exactly which keypaths to process
Use KVC validation to reject the error mapping (not ideal as the error isn't really captured)
For the dynamic mapping option, the dynamic mapping has the forceCollectionMapping option set and it checks the top level key available and returns the appropriate mapping (which wouldn't have forceCollectionMapping set and which uses addAttributeMappingFromKeyOfRepresentationToAttribute:).
I got it to work using Wain's first suggestion. Here's the solution if anyone has the same issue:
I created a dynamic mapping like this:
RKDynamicMapping *dynamicMapping = [RKDynamicMapping new];
dynamicMapping.forceCollectionMapping = YES;
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping * (id representation) {
if ([representation valueForKey:#"watchnow"] || [representation valueForKey:#"upcoming"] || [representation valueForKey:#"rent"]) {
return searchResultsMapping;
}
return nil;
}];
As you can see in my example at the top, I'm only interested in keys named "watchnow", "upcoming" or "rent".
The searchResultsMapping which is returned is configured like this:
RKObjectMapping *searchResultsMapping = [RKObjectMapping mappingForClass:[TDXSearchResults class]];
[searchResultsMapping addAttributeMappingFromKeyOfRepresentationToAttribute:#"searchSection"];
So I now end up with three SearchResult objects with either "watchnow", "upcoming" or "rent" in their searchSection NSString property.

Restkit/Core Data relationship mappings, entity to same type of entity (parent/child)

I am following the Alexander Edge tutorial on RestKit 0.2.0 but I am confused about how to apply it to my needs. Specifically, I am consuming a web service that returns objects in the following structure:
{
"ObjectIdMember": 200,
"ObjectNameMember": "Baseball Bat",
"SubObjectIdMember": 4124
},
{
"ObjectIdMember": 200,
"ObjectNameMember": "Baseball Glove",
"SubObjectIdMember": 4555
},
The idea is that an Object entity can have many sub-objects. Roughly speaking, the purpose of getting the Object is to use the DisplayName to populate section headers in a table view, and group sub-objects in sections by object.
How do I capture this sort of relationship (or define it) using RestKit + Core Data? The tutorial only suggests what you might do if there is a subobject defined in the response, but this is a different situation.
I know that I could just decorate and use a subclass of Object with a -(NSArray *)getSubObjects, but Core Data would not be aware of what I was doing in the sense that this would not be using any relationships.
I believe what you want is RKConnectionDescription, which can establish a relationship in Core Data using foreign keys.
The example in the docs gives the following json:
{ "project":
{ "id": 12345,
"name": "My Project",
"userID": 1
}
}
with the following mapping configuration:
NSEntityDescription *projectEntity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:managedObjectContext];
NSRelationshipDescription *userRelationship = [projectEntity relationshipsByName][#"user"];
RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:userRelationship attributes:#{ #"userID": #"userID" }];

RestKit Foreign Key Connections to-many and to-one

My goal is to implement one-to-many and many-to-one relationship connection with RestKit. I'm using version 0.20pre6.
This page http://restkit.org/api/0.20.0/Classes/RKConnectionDescription.html#overview reports half example.
First example is many-to-one.
json:
{ "project":
{ "id": 12345,
"name": "My Project",
"userID": 1
}
}
code:
NSEntityDescription *projectEntity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:managedObjectContext];
NSRelationshipDescription *userRelationship = [projectEntity relationshipsByName][#"user"];
RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:userRelationship attributes:#{ #"userID": #"userID" }];
The thing i missed during my first attempt is that userID needs to be in the Entity too. Otherwise it won't work. I don't really understand why... anyway it works.
My problem is related to the second example which is a one-to-many. Json example:
{ "project":
{ "id": 12345,
"name": "My Project",
"userID": 1,
"teamMemberIDs": [1, 2, 3, 4]
}
}
code:
NSEntityDescription *projectEntity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:managedObjectContext];
NSRelationshipDescription *teamMembers = [projectEntity relationshipsByName][#"teamMembers"]; // To many relationship for the `User` entity
RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:teamMembers attributes:#{ #"teamMemberIDs": #"userID" }];
Now... teamMemberIDs needs to be in the Entity definition just like userID in the previous example. Here are my questions:
How do I define teamMemberIDs since it's an array of values?
Is there a working example about this things?? The examples directory inside RestKit library only shows nested relationships.
Am I doing right? Am I missing something big?
I was struggling with this exact same problem, but was eventually able to find solution. Hopefully this will help you out.
Using the example:
You must have an NSArray property of Project, your NSManagedObject,
where you can map the teamMemberIDs to. To do this you make a transformable property.
Map teamMemberIDs to that property as you would a primitive.
Create a connectionDescription just as done in the example.

Resources