I've been working with RestKit (0.9.3) to populate some Core Data in my app. The entity objects are created correctly, but the relationships between them are not created. From what I can tell, I have things setup correctly according to:
https://github.com/RestKit/RestKit/wiki/Object-mapping
RKRelationshipMappingExample in the Examples folder
So, here's a sample of the JSON that I'm getting back from the server:
{
"artist" : {
"artistId" : 123,
"artistName" : "The Artist",
"albums" : [
{
"albumPrice" : 12.99,
"albumId" : 456,
"trackCount" : 12,
"albumName" : "Album Title",
},
{
"albumPrice" : 10.99,
"albumId" : 789,
"trackCount" : 10,
"albumName" : "Another Album Title",
}
]
}
}
And I am using RestKit to populate the following two managed objects:
#interface Artist : NSManagedObject
#property (nonatomic, retain) NSNumber * artistId;
#property (nonatomic, retain) NSString * artistName;
#property (nonatomic, retain) NSSet *albums;
#end
#interface Artist (CoreDataGeneratedAccessors)
- (void)addAlbumsObject:(Album *)value;
- (void)removeAlbumsObject:(Album *)value;
- (void)addAlbums:(NSSet *)values;
- (void)removeAlbums:(NSSet *)values;
#end
#interface Album : NSManagedObject
#property (nonatomic, retain) NSNumber * albumId;
#property (nonatomic, retain) NSString * albumName;
#property (nonatomic, retain) NSNumber * albumPrice;
#property (nonatomic, retain) NSNumber * trackCount;
#property (nonatomic, retain) NSManagedObject *artist;
#end
I've setup the following configurations with RestKit, and the intent is for 3 Core Data items to be created: one artist and two albums. The artist entity has a "albums" relationship which should be a set of these two albums.
// Initialize our RestKit singleton
RKClient *client = [RKClient clientWithBaseURL:API_URL];
// Initialize for Object mapping
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:API_URL];
objectManager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"AppName.sqlite"];
// Configure ALBUM mapping
RKManagedObjectMapping* albumMapping = [RKManagedObjectMapping mappingForClass:[Album class]];
albumMapping.primaryKeyAttribute = #"albumId";
[albumMapping mapKeyPath:#"albumId" toAttribute:#"albumId"];
[albumMapping mapKeyPath:#"albumName" toAttribute:#"albumName"];
[albumMapping mapKeyPath:#"albumPrice" toAttribute:#"albumPrice"];
[albumMapping mapKeyPath:#"trackCount" toAttribute:#"trackCount"];
// Configure ARTIST mapping
RKManagedObjectMapping* artistMapping = [RKManagedObjectMapping mappingForClass:[Artist class]];
artistMapping.primaryKeyAttribute = #"artistId";
[artistMapping mapKeyPath:#"artistId" toAttribute:#"artistId"];
[artistMapping mapKeyPath:#"artistName" toAttribute:#"artistName"];
[artistMapping mapKeyPath:#"albums" toRelationship:#"albums" withMapping:albumMapping];
// Register mappings with the provider
[[RKObjectManager sharedManager].mappingProvider setMapping:albumMapping forKeyPath:#"albums"];
[[RKObjectManager sharedManager].mappingProvider setMapping:artistMapping forKeyPath:#"artist"];
And then later in my program, I load the data:
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager loadObjectsAtResourcePath:#"/artist/123/albums" delegate:self];
After this is executed, my Core Data store is populated with the three objects (one artist and two albums), but the relationship between them is not created.
The result is that I can fetch this list of albums directly, or I can fetch the artist directly. But if I try to use: artist.albums the result is empty.
Can anyone offer suggestions on what I could be doing wrong, or how to further troubleshoot this?
Thanks so much.
EDIT: Update 10/26
I just setup a small sample project with these same configurations and everything was loaded correctly (i.e. The artist entity was created with a relationship to it's albums). Sadly, this means that the issue is something in my larger project, rather than in what I outlined here earlier. With that said, I'll continue trudging though and updating, so if anyone has advice on how to troubleshoot this or where in particular to look, I would greatly appreciate it.
EDIT: Resolved
Embarrassingly, the problem was simply that I was using the wrong base url. I'll post full details (in case it can help anyone else) tomorrow.
Well... we have a resolution. I'm embarrassed to say that the problem was simply that I was using the wrong base url in my main project. I made some updates to the JSON returned by the local copy of the API, but the base url was still pointing to the copy of the API online.
The change I made was to format the JSON this way rather than having "artist" and "albums" be separate entities in the JSON.
Related
I have questions about rest kit.
do i need to create mapping for all objects of json that i get ? or i can provide mapping only fore some of this objects and restkit will work properly even if I did not create mapping for some objects that I am loading from internet?
for example there are this classes
#interface RKAUser : NSObject
#property (strong, nonatomic) NSString* nameString;
#property (strong, nonatomic) RKCard* card;
#end
#interface RKACard : NSObject
#property (strong, nonatomic) NSString* numberString;
#end
+ (RKMapping *)userMapping {
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKAUser
class]];
[mapping addAttributeMappingsFromArray:#[#"nameString"]];
return mapping;
}
I have json where is information for about 20 users
in this json there are all user information including nameString and his cards numberString.
but I create mapping only for nameString and did not create mapping for card
question for this example will be would this code work or it will crash maybe because I did not provide all mapping
another question
can i use restkit for mapping any json information or not?
does information should be definitely from some kind of rest server or rest kit can map json information from any server? will it work if information is not from server?
You only need to implement properties that you used when creating your mappings.
JSON doesn't need to come from the server. You can take any JSON document, then parse it yourself and use RKMapperOperation with the result.
RKMapperOperation *operation = [[RKMapperOperation alloc] initWithRepresentation:parsedJSON mappingsDictionary:yourMappingsDictionary];
NSError *error = nil;
[operation execute:&error];
if (!error) {
NSArray *result = mapper.mappingResult.array;
}
This question already has an answer here:
Restkit request not sending parameters
(1 answer)
Closed 8 years ago.
I have the following two entities
#interface MEContactInfo : NSObject
#property (nonatomic,strong) NSString* phone ;
#property (nonatomic,strong) NSString* email;
#end
#interface MEContact : NSObject
#property (nonatomic,strong) NSString* _id ;
#property (nonatomic,strong) NSString* lastName;
#property (nonatomic,strong) NSString* firstName;
#property (nonatomic,strong) NSString* data ;
#property (nonatomic,strong) NSMutableArray* contactInfos ;
#end
The second entity contact contains the array of contact infos. Now I want to post this to my server but I am not able to do so. My mappings are as following:
RKObjectMapping* contactMapping = [RKObjectMapping mappingForClass:[MEContact class]];
[contactMapping addAttributeMappingsFromArray:#[#"_id",#"lastName",#"firstName",#"data"]];
RKObjectMapping* contactInfosMapping = [RKObjectMapping mappingForClass:[MEContactInfo class]];
[contactInfosMapping addAttributeMappingsFromArray:#[#"email",#"phone"]];
[contactMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"contactInfos" toKeyPath:#"contactInfos" withMapping:contactInfosMapping]];
My request descriptor is as following:
requestDescriptor = [RKRequestDescriptor
requestDescriptorWithMapping:[contactMapping inverseMapping]
objectClass: [MEContact class]
rootKeyPath:nil method:RKRequestMethodAny];
Now when I post something like this:
{ firstName:”abc”,
lastName:”xyz”,
contactInfos: [{
email:”test#test.com”,
phone:”9999999999”
}]
}
I receive
{
firstName:”abc”,
lastName:”xyz”,
contactInfos: [ ”test#test.com”,”9999999999”]
}
If I have multiple entries in the contactInfos array, they all are appended to the contactInfos array I receive on the server side. Basically the contactInfo object is flattening in an array. Can you please let me know how I can fix this.
I got it to work. Everything above was correct. The problem was that data was not going as JSON to the server. The solution was that I had to set request serialization MimeType which can be done by doing this
[objectManger setRequestSerializationMIMEType:RKMIMETypeJSON];
Thanks
I have create an composite object (tree structure) in visual studio and stored in an sql server database with.
here are the screenshots:
now I want to create this model schema in core data and store the received data with RestKi.
here what I have designed with Xcode:
could someone help me to store this data with RestKit which I get in json ?
//Initialize managed object store
.
.
.
//Complete Core Data stack initialization
.
.
.
RKEntityMapping *menuMapping = [RKEntityMapping mappingForEntityForName:#"Menu" inManagedObjectStore:managedObjectStore];
menuMapping.identificationAttributes = #[#"menuComponentID"];
[menuMapping addAttributeMappingsFromDictionary:#{
#"ID":#"menuComponentID",
#"Name":#"name",
#"Description":#"descriptions",
#"Thumbnail":#"thumbnail",
#"Image": #"image",
}];
RKEntityMapping *menuItemMapping = [RKEntityMapping mappingForEntityForName:#"MenuItem" inManagedObjectStore:managedObjectStore];
[menuItemMapping addAttributeMappingsFromDictionary:#{
#"ID" : #"menuComponentID",
#"Description" : #"descriptions",
#"Name" : #"name",
#"Thumbnail":#"thumbnail",
#"Image":#"image",
#"Calorie":#"calorie",
#"Vegeterian":#"vegeterian",
}];
RKRelationshipMapping *menuRelationShip = [RKRelationshipMapping relationshipMappingFromKeyPath:#"MenuComponents" toKeyPath:#"children" withMapping:menuMapping];
[menuMapping addPropertyMapping:menuRelationShip];
RKResponseDescriptor *menuResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:menuMapping
method:RKRequestMethodGET
pathPattern:#"/api/menu/"
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:menuResponseDescriptor];
managed objects:
menuComponnet.h
#class MenuComponent;
#interface MenuComponent : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * descriptions;
#property (nonatomic, retain) NSString * image;
#property (nonatomic, retain) NSString * thumbnail;
#property (nonatomic, retain) NSString * menuComponentID;
#property (nonatomic, retain) MenuComponent *parent;
#end
////
menu.h
#class MenuComponent;
#interface Menu : MenuComponent
#property (nonatomic, retain) NSSet *children;
#end
#interface Menu (CoreDataGeneratedAccessors)
- (void)addChildrenObject:(MenuComponent *)value;
- (void)removeChildrenObject:(MenuComponent *)value;
- (void)addChildren:(NSSet *)values;
- (void)removeChildren:(NSSet *)values;
#end
menuItem.h
#interface MenuItem : MenuComponent
#property (nonatomic, retain) NSNumber * price;
#property (nonatomic, retain) NSNumber * calorie;
#property (nonatomic, retain) NSNumber * vegeterian;
#end
when I browse the created sqlite data base. I see there is only one created table name menuComponent which has all attributes of all menu and and menuItem. while nhibernate bas created for me three table as you see in the above picture.
and parent field for all of the records is null!!
how can I successfully store this data? please explain. I'm new to RestKit.
Default JSON I got:
[{"ID":"213028b8-9f59-4d7f-bae9-a2c301815a43","MenuComponents":[{"ID":"d1a861d3-d9e8-41a4-9577-a2c301815a44","MenuComponents":[{"ID":"fa6d172d-ab26-4782-8306-a2c301815a44","Vegetarian":false,"Calorie":0,"Name":"Double Chocolate Cake","Description":"With a melted heart of chocolate","Parent":{"ID":"d1a861d3-d9e8-41a4-9577-a2c301815a44"},"Version":"AAAAAAAAF64="},{"ID":"f9050f6b-8419-4457-9ebd-a2c301815a44","Vegetarian":false,"Calorie":0,"Name":"Tiramisu","Description":"Lady finger cookies are soaked in coffee liqueur and rum","Parent":{"ID":"d1a861d3-d9e8-41a4-9577-a2c301815a44"},"Version":"AAAAAAAAF60="}],"Name":"Dessets Menu","Description":"Desserts for serving after meal","Parent":{"ID":"213028b8-9f59-4d7f-bae9-a2c301815a43"},"Version":"AAAAAAAAF6o="},{"ID":"1f50565a-eeb2-4ab0-ba7b-a2c301815a44","MenuComponents":[{"ID":"0352c5a2-081a-496d-86aa-a2c301815a44","Vegetarian":true,"Calorie":0,"Name":"Grilled Chicken Salad","Description":"Sliced, grilled chicken tops a fresh salad mix, peas","Parent":{"ID":"1f50565a-eeb2-4ab0-ba7b-a2c301815a44"},"Version":"AAAAAAAAF6w="},{"ID":"4fad8aa0-285f-4419-b85e-a2c301815a44","Vegetarian":true,"Calorie":0,"Name":"Carolina Chicken Salad","Description":"Salad mix, fried chicken tenders, peas, cheddar cheese","Parent":{"ID":"1f50565a-eeb2-4ab0-ba7b-a2c301815a44"},"Version":"AAAAAAAAF6s="}],"Name":"Salad & Combinations","Description":"Salad & Combinations","Parent":{"ID":"213028b8-9f59-4d7f-bae9-a2c301815a43"},"Version":"AAAAAAAAF6k="}],"Name":"Lunch Menu","Description":"Lunch Super Menu","Version":"AAAAAAAAF6E="},{"ID":"fa6d172d-ab26-4782-8306-a2c301815a44"},{"ID":"0352c5a2-081a-496d-86aa-a2c301815a44"},{"ID":"d1a861d3-d9e8-41a4-9577-a2c301815a44"},{"ID":"f9050f6b-8419-4457-9ebd-a2c301815a44"},{"ID":"4fad8aa0-285f-4419-b85e-a2c301815a44"},{"ID":"1f50565a-eeb2-4ab0-ba7b-a2c301815a44"}]
you can view the json here:
http://jsonviewer.stack.hu/
UPDATE:
here is menuComponent RelationShip
RKRelationshipMapping *menuComponentRelationShip = [RKRelationshipMapping relationshipMappingFromKeyPath:#"MenuComponents" toKeyPath:#"children" withMapping:menuComponentMapping];
[menuComponentMapping addPropertyMapping:menuComponentRelationShip];
but I must map menuItem too because some property are exclusively for menuItem.
RKEntityMapping *menuItemMapping = [RKEntityMapping mappingForEntityForName:#"MenuItem" inManagedObjectStore:managedObjectStore];
[menuItemMapping addAttributeMappingsFromDictionary:#{
#"ID" : #"menuComponentID",
#"Description" : #"descriptions",
#"Name" : #"name",
#"Thumbnail":#"thumbnail",
#"Image":#"image",
#"Prcie":#"price",
#"Calorie":#"calorie",
#"Vegeterian":#"vegeterian",
}];
RKResponseDescriptor *menuItemresponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:menuItemMapping
method:RKRequestMethodGET
pathPattern:#"/api/menu/"
keyPath:nil
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:menuItemresponseDescriptor];
but after adding this responseDescriptor to mobjetManager only addes one super menu!!
How can I map menuItem and add them to database too?
Your issue doesn't seem to be related to RestKit at all, rather it is misunderstandings about Core Data.
Your children relationship should be on menu component and the inverse of the parent relationship. This allows Core Data to manage the relationships correctly (and as you expect).
You should only have 1 table in the SQLite DB file because you are using entity inheritance (it isn't a problem, just an implementation detail).
So just change the relationship in the model and you should be good to go.
I am trying to use restkit to read/parse an xml feed. which contains a nested array of artists. I can map other relationships but am unclear what I am to do with the artist relationship below.
My dataset looks like:
<JamBase_Data>
<Results_Title>Jersey City, NJ</Results_Title>
<event>
<event_id>1896611</event_id>
<artists>
<artist>
<artist_id>96929</artist_id>
<artist_name>The Happy Problem</artist_name>
</artist>
<artist>
<artist_id>29817</artist_id>
<artist_name>Craig Greenberg</artist_name>
</artist>
</artists>
<event_date>1/9/2013</event_date>
<venue>
<venue_id>48553</venue_id>
<venue_name>The Cake Shop</venue_name>
<venue_city>New York</venue_city>
<venue_state>NY</venue_state>
<venue_zip>10002</venue_zip>
</venue>
<event_url>
http://www.jambase.com/Shows/Event.aspx?eventID=1896611
</event_url>
</event>
</JamBase_Data>
Mappings:
RKObjectMapping *eventMapping = [RKObjectMapping mappingForClass:[Event class]];
RKObjectMapping *artistMapping = [RKObjectMapping mappingForClass:[Artist class]];
RKObjectMapping *venueMapping = [RKObjectMapping mappingForClass:[Venue class]];
[artistMapping mapKeyPathsToAttributes: #"artist_name",#"artist_name", nil];
[venueMapping mapKeyPathsToAttributes: #"venue_city",#"venue_city",#"venue_state",#"venue_state", #"venue_name", #"venue_name", nil];
[objectManager.mappingProvider setMapping:eventMapping forKeyPath:#"JamBase_Data.event"];
[eventMapping mapKeyPath:#"artists" toRelationship:#"artists" withMapping:artistMapping];
[eventMapping mapKeyPath:#"venue" toRelationship:#"venue" withMapping:venueMapping];
Venue.h
#interface Event : NSObject
#property (nonatomic, strong) NSSet *artists;
#property (strong, nonatomic) Venue *venue;
#end
Artist.h
#interface Artist : NSObject
#property (nonatomic, strong) NSNumber *artist_id;
#property (nonatomic, strong) NSNumber *artist_name;
I can get venue to map correctly, but cannot figure out how to get Artists to map. Is there something i need to do to tell the object manager to make an coming off of the event?
Any help would be greatly appreciated as this is my first foray into iOS development.
The Artists object you see in the XML translates to an NSArray. Now that contains objects of type Artists. Have you thought about it that way?
I have 2 autogenerated entities :
#interface ContactEntity : Entity
#property (nonatomic, retain) NSString *caption;
#property (nonatomic, retain) NSString *image;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *text;
#property (nonatomic, retain) NSSet *pointLink;
#end
#interface PointEntity : Entity
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#property (nonatomic, retain) NSSet *entityLink;
#property (nonatomic, retain) EntityEntity *entityTypeLink;
#end
They are linked between each other as ManyToMany, i.e. one contact has many points, one point has many contacts inside.
Then a i get first entity :
ContactModel *contact = [[[ContactModel alloc] init] autorelease];
// this is FetchRequest, returns array of all entities
self.items = [contact list:contact];
// i get only one, all is OK here, this entity has related PointEntity in DB
ContactEntity *contactEntity = [self.items objectAtIndex:self.selection];
And when i try to get related PointEntity using NSSet in selected ContactEntity i always get NULL or empty array. Neither of this works :
NSArray *points = [contactEntity.pointLink allObjects];
PointEntity *pointEntity = [contactEntity.pointLink anyObject];
NSInteger x1 = [points count]; // always 0
id x2 = pointEntity.latitude; // always 0
for (PointEntity *x in contactEntity.pointLink) // isn't enumerated because count = 0
{
id x3 = x.latitude;
}
Any thoughts are appreciated. Did i miss something, maybe i need to use NSPredicate to select entities from PointEntity that are related to ContactEntity?
Thanks.
P.S. My question is similar to this but that suggestion does not work for me, i cannot get loaded associated entities using NSSet of main entity :(
CoreData: many-to-many relationship
Answer is found ... i tried to use property of the autogenerated entities when created new records in CoreData, in the meantime the correct way is to use generated methods like - addPointLinkObject, addEntityLinkObject, etc
Example, i have 3 tables :
Contacts (one person may have many locations)
<< - >>
Points (one location can contain many people)
<< - >
EntityTypes (just a type of a location, in this case type is CONTACT)
One of the entities autogenerated by xCode :
#interface PointEntity : Entity
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#property (nonatomic, retain) NSSet *entityLink; // reference to Contacts table (ManyToMany)
#property (nonatomic, retain) EntityEntity *entityTypeLink; // reference to EntityType table (OneToMany)
#end
#interface PointEntity (CoreDataGeneratedAccessors)
- (void)addEntityLinkObject:(ContactEntity *)value;
- (void)removeEntityLinkObject:(ContactEntity *)value;
- (void)addEntityLink:(NSSet *)values;
- (void)removeEntityLink:(NSSet *)values;
#end
I tried to do the following :
// create 3 new instances - one for each entity
ContactEntity *contactEntity = [model create:model];
PointEntity *pointEntity = [point create:point];
EntityModel *entity = [[[EntityModel alloc] init] autorelease];
entity.name = model.table;
EntityEntity *entityEntity = [[entity list:entity] objectAtIndex:0];
// then i tried to use entity's properties directly to bind entities
// it works, but it works only on DB level when we add new records, but somehow something was missed and thus such selection did not work later - [pointEntity allObjects]
//pointEntity.entityTypeLink = entityEntity; // WRONG !!!
//pointEntity.entityLink = contactEntity.pointLink;
//contactEntity.pointLink = pointEntity.entityLink;
// then i replaced 3 lines above with these ones
[pointEntity addEntityLinkObject:contactEntity]; // CORRECT !!!
[contactEntity addPointLinkObject:pointEntity];
[entityEntity addPointLinkObject:pointEntity];
[context save]; // save changes made with entities in current CoreData context
// now [pointEntity allObjects] and [pointEntity anyObject] work as expected
Useful links -
Coredata and Generated subclass for NSManagedObject with relations
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdAccessorMethods.html#//apple_ref/doc/uid/TP40002154