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.
Related
I want to show data as it came from the backend So let's have an example json file:
{
"fonts": [
{
"name": "Helvetica",
"styleIdentifier": "H0",
"size": 17
},
{
"name": "Helvetica",
"styleIdentifier": "H1",
"size": 14
},
{
"name": "Helvetica-Bold",
"styleIdentifier": "H0Bold",
"size": 17
},
{
"name": "HelveticaNeue-Light",
"styleIdentifier": "H0Light",
"size": 40
}
]
}
So i create a relationship (many - many) with ordered option selected. And by the input i see it's always write in the same way to Core Data, but when I try to fetch it
configuratation.fonts where fonts is a NSOrderedSet i get items in completly random order. I miss sth in spec? Or I should sort it somehow?
__EDIT__
Firstly when i get a data from above json I have a configuration set with empty font relation. Then I fetch this and insert it into core data with:
NSMutableArray *returnArray = [NSMutableArray new];
for(NSDictionary *fontDictionary in jsonArray) {
Font *fontObj = [Font font:fontDictionary inContext:context];
[returnArray addObject:fontObj];
}
And in this array data is in correct order. Then in configuration object i add it to NSOrderedSet by:
-(void)appendTracks:(NSArray<Font*>*)fontArray {
self.fonts = [NSOrderedSet orderedSetWithArray: fontArray];
}
And then i try to fetch it by simply use reference:
configuration.fonts
And in this step data are completly not in correct order.
Do not set the NSOrderedSet directly.
Either modify the existing set:
[self.fonts addObjectsFromArray:fontArray];
Or:
Xcode generates methods to add and remove entries from ordered sets within the generated NSManagedObject class.
Assuming you have an Entity called ManagedConfiguration which holds an ordered to many relation called fonts:
ManagedConfiguration *managedObjectConfigurationInstance = //create or fetch configuration in ManagedObjectContext
NSOrderedSet<ManagedFont> *fonts = //created or fetched fonts in wanted order
managedObjectConfigurationInstance.addToFonts(fonts)
replaceFonts, removeFromFontsAtIndex aso. methods are also generated.
Depending on your requirements, you might want to store the fonts in random order and apply a NSSortDescriptor to your NSFetchRequest to fetch the data in a specific order.
Instead of trying to set the data directly to your property(fonts), you need to first fetch the mutable copy of your NSOrderedSet from the NSmanagedObject Subclass (I assume it to be Font).
NSMutableOrderedSet *orderedSet = [self mutableOrderedSetValueForKey:#"fonts"];
Then add the objects from the array to this orderedSet.
[orderedSet addObjectsFromArray:array];
Now you would have properly set the the values for the key fonts.
So your appendTracks function would now look like this.
-(void)appendTracks:(NSArray<Font*>*)fontArray {
NSMutableOrderedSet *orderedSet = [self mutableOrderedSetValueForKey:#"fonts"];
[orderedSet addObjectsFromArray:fontArray];
}
Now execute your fetch request. You should receive the data in the set order in the array.
PS:I had used your JSON response to test this.
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.
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.
I have two Entities one called Games and one called Teams. The Games entity has a to one relationship to Teams called teams and the Teams entity has a to many relationship to Games called games. (A team can be in many games but a game can only have 1 team. I am using a separate entity for Opponents)
I am selecting a team by using it's ID. Here is my code for adding a team to the Games entity:
Games *newGame = (Games *) [NSEntityDescription insertNewObjectForEntityForName:#"Games" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *fetchTeams = [[NSFetchRequest alloc] init];
NSEntityDescription *fetchedTeam = [NSEntityDescription entityForName:#"Teams"
inManagedObjectContext:self.managedObjectContext];
[fetchTeams setEntity:fetchedTeam];
NSArray *fetchedTeams = [self.managedObjectContext executeFetchRequest:fetchTeams error:&error];
for (Teams *myTeam in fetchedTeams) {
if (myTeam.teamID == teamid){
newGame.teams = myTeam;
}
}
The error I am getting is: 'NSInvalidArgumentException', reason: 'The left hand side for an ALL or ANY operator must be either an NSArray or an NSSet.'
I don't understand it, newGame.teams is an object of Teams , it is not an NSSet. If I was doing Teams.games it would be an NSSet.
What am I doing wrong?
You've not described what is the data type of variable "teamid" here. Hence, I'm assuming that it must be some primitive type int.
Based on this assumption, you can make the following changes in your code:
if(fetchedTeams!=nil)
{
if(fetchedTeams.count>0)
{
for(Teams *myTeam in fetchedTeams)
{
//check here because coredata stores a number in NSNumber object.
//Hence you've to get the intValue to make a equality check, like below
if(myTeam.teamID.intValue == teamid)
{
//do your stuff here.
//Also check the last part of my answer, I have a question here.
}
}
}
}
In your code you have "newGame.teams = myTeam". What is the data type of "teams" in "newGame" ? is it Teams* or NSSet* ?
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" }];