Nested to-many relationships with RestKit - ios

I'm trying to map relationships for the following JSON to a CoreData backed model.
Here's the JSON:
{
"photos": [
{
"id": 1,
"thumb": "http://localhost/test_1_thumb.png",
"image": "http://localhost/test_1.png",
"date": "2011-07-02T06:06:16Z",
"likes": 0,
"user": {
"id": 1,
"username": "User1",
"avatar": "http://cdn.domain.com/avatar.jpg"
},
"comments": [
{
"date": "2011-07-02T06:06:16Z",
"text": "This is the only comment",
"id": 1,
"author": {
"username": "User1",
"id": 1,
"avatar": "http://cdn.domain.com/avatar.jpg"
}
}
]
}
]
}
My CoreData models which I've mapped with RestKit's OM2 classes.
The Photo class.
#interface Photo : NSManagedObject
#property (nonatomic, retain) NSString * thumbnailUrl;
#property (nonatomic, retain) NSString * imageUrl;
#property (nonatomic, retain) NSDate * date;
#property (nonatomic, retain) NSNumber * likes;
#property (nonatomic, retain) NSNumber * photoID;
#property (nonatomic, retain) User *owner;
#property (nonatomic, retain) NSSet *comments;
#end
#interface Photo (CoreDataGeneratedAccessors)
- (void)addCommentsObject:(Comment *)value;
- (void)removeCommentsObject:(Comment *)value;
- (void)addComments:(NSSet *)values;
- (void)removeComments:(NSSet *)values;
#end
#implementation Photo
#dynamic thumbnailUrl;
#dynamic imageUrl;
#dynamic date;
#dynamic likes;
#dynamic photoID;
#dynamic owner;
#dynamic comments;
#end
Now the part I'm not completely sure on is the relationships:
// Map relationships between entities.
[commentMapping mapKeyPath:#"author" toRelationship:#"author" withMapping:userMapping];
[photoMapping mapKeyPath:#"user" toRelationship:#"owner" withMapping:userMapping];
[photoMapping mapKeyPath:#"comments" toRelationship:#"comments" withMapping:commentMapping];
With this I'm able to access all but the comments attribute of a photo, with this error: Comments = Relationship 'comments' fault on managed object. I have a feeling this is to do with the Photo model having comments defined as an NSSet or I'm doing something wrong with the relationship mapping but I'm not sure.

So turns out that the problem was nothing to do with RestKit or the mapping but my understanding of how CoreData functions.
I was accessing the mapped JSON objects in my delgate as follows:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
NSLog(#"Loaded photos: %#", objects);
for (id object in objects) {
Photo *photo = object;
// Print other photo attributes...
NSLog(#"Comments = %#", photo.comments );
}
}
The problem was in trying to print the collection object directly. CoreData would not fire a fault on the data store. Simply replacing with a fast iteration as follows and accessing the properties allowed me to access the comments object.
for (Photo *photo in objects) {
for (Comment *comment in photo.comments) NSLog(#"Comment = %#", comment.text);
}

You need something like:
[objectManager.mappingProvider setMapping:commentMapping forKeyPath:#"comments"];
Also checkout
https://github.com/RestKit/RestKit/wiki/Object-mapping

Related

[__NSSingleEntryDictionaryI elements]: unrecognized selector sent to instance 0x2836c2820

Well, I should remember you I am still a beginner in iOS development with objective c. And maybe because of this, the solution is simple but I can´t see it. I have a json who is armed in the following way :
{
"origin_addresses": [
"Test"
],
"rows": [
{
"elements": [
{
"distance": {
"text": "0.3 km",
"value": 339
},
"duration": {
"text": "1 min",
"value": 82
},
"status": "OK"
}
]
}
],
"status": "OK"
}
The app can consume the service without problems. My goal is to reach the distance attribute. Which I do as follows. I use a foreach to get through the array rows and then another foreach to get through the array elements.
for (BPCGARows *item in googleAddress.rows) {
NSLog(#"rows : %#", item);
for (BPCGAElements *element in item.elements) {
}
}
When I'm debugging the app, my first for each works without problems, but when I access the second foreach the (BPCGAElements * element in item.elements) the app crashes. Finally I can't print the item.elements in a log either. The error message is as follows:
'NSInvalidArgumentException', reason: '-[__NSSingleEntryDictionaryI
elements]: unrecognized selector sent to instance 0x281c75da0'
header BPCGADistance :
#interface BPCGADistance : MBOInspectableModel
#property (nonatomic, strong, readonly) NSString *text;
#property (nonatomic, strong, readonly) NSNumber *value;
#end
main BPCGADistance:
#interface BPCGADistance()
#property (nonatomic, strong, setter=text:) NSString *text;
#property (nonatomic, strong, setter=value:) NSNumber *value;
#end
header BPCGAElements :
#interface BPCGADistance : MBOInspectableModel
#property (nonatomic, strong, readonly) NSString *text;
#property (nonatomic, strong, readonly) NSNumber *value;
#end
main BPCGAElements:
#interface BPCGADistance()
#property (nonatomic, strong, setter=text:) NSString *text;
#property (nonatomic, strong, setter=value:) NSNumber *value;
#end
This is my NSLog(#"rows : %#", item),
I emphasize that by using the item.elements to print the crashea app on the console. ;

Not able to use one-to-many relationship in sharkORM

I'm using KeyValueObjectMapping to convert JSON string to Model class.
Here is JSON string:
{
"id_str": "123456",
"name": "Some Name",
"protected": false,
"created_at": "Tue Mar 31 18:01:12 +0000 2009",
"tweets" : [
{
"created_at" : "Sat Apr 14 00:20:07 +0000 2012",
"id_str" : 190957570511478784,
"text" : "Tweet text",
"comments": {
"id_str":"2343",
"text":"This is comment1"
}
},
{
"created_at" : "Sat Apr 14 00:20:07 +0000 2012",
"id_str" : 190957570511478784,
"text" : "Tweet text",
"comments": {
"id_str":"2343",
"text":"This is comment2"
}
}
]
}
For which I have created model classes, like this:
For User,
#interface User : SRKObject
#property(nonatomic, strong) NSString *idStr;
#property(nonatomic, strong) NSString *name;
#property(nonatomic, strong) BOOL protected;
#property(nonatomic, strong) NSDate *createdAt;
#property(nonatomic, strong) NSArray *tweets;
#end
For tweets,
#interface Tweet : SRKObject
#property(nonatomic, strong) NSString *idStr;
#property(nonatomic, strong) NSString *text;
#property(nonatomic, strong) NSDate *createdAt;
#property(nonatomic, strong) Comments *comments;
#end
For Comments,
#interface Comments : SRKObject
#property(nonatomic, strong) NSString *idStr;
#property(nonatomic, strong) NSString *text;
#end
It is working absolutely fine and I'm getting tweets in array. And I get tweets as user.tweets.
To save this in database, I'm using SharkORM for ORM. But while saving either it crash or don't save tweets at all.
Here is the issue open in repo (but using different example) - https://github.com/sharksync/sharkorm/issues/78
The reason for the crash was the persistence of an array of Tweets stored in a User object as opposed to storing them as a separate class. See: This
Ultimately what you want to end up with is ....
#interface Tweet : NSObject
#property(nonatomic, strong) User* user;
#property(nonatomic, strong) NSString *idStr;
#property(nonatomic, strong) NSString *text;
#property(nonatomic, strong) NSDate *createdAt;
#end
As storing all tweets in an array object will just tie you in knots quickly (no searching, ever increasing save times).
So, when you get the JSON down, you can map the first class no problem, then iterate the tweets creating objects which are related to the User class.
Relating objects is just a case of setting the .user property with the relevant User entity which you have either created or queried for.
Thanks to #Adrian_H for the efforts. I figured out the solution and posting here.
I have added below code in Tweet & Comments model to save tweets as NSArray and comments as model object and it worked.
- (void)encodeWithCoder:(NSCoder *)aCoder;
-(id)initWithCoder:(NSCoder *)aDecoder;

How to declare optional property in Mantle?

Specific situation detail:
In my current photo app, There are such situation : allow user experience first without log in,
but no have some field(e.g:voted,purchased ,etc) in the JSON responded from backend. If user logged in,
the field is added in JSON .
I'll show off my implement details as following:
PhotoListResponseModel (directly inherit MTLModel)
#interface PhotoListResponseModel : MTLModel <MTLJSONSerializing>
...
#property (nonatomic, copy, readonly) NSNumber *foo;
#property (nonatomic, copy, readonly) NSArray<PhotoModel *> *subPhotos;
#end
#interface PhotoModel : MTLModel <MTLJSONSerializing>
#property (nonatomic, copy, readonly) NSNumber *photoID;
#property (nonatomic, copy, readonly) NSURL *imageUrl;
...
#end
#implementation PhotoListResponseModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"foo": #"foo"
#"subPhotos": #"photos"
};
}
+ (NSValueTransformer *)subPhotosJSONTransformer {
return [MTLJSONAdapter arrayTransformerWithModelClass:PhotoModel.class];
}
#end
#implementation PhotoModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"photoID": #"id",
#"userID": #"user_id",
};
}
#end
Correspondly, the JSON example as following:
noAuthPhoto.json
{
"foo": 1,
"photos": [
{
"id": 204748881,
"image_url": "https://foo1/bar1",
},
{
"id": 204996257,
"image_url": "https://foo2/bar2"
}
],
…
…
}
AuthedPhoto.json
{
"foo": 1,
"photos": [
{
"id": 204748881,
"image_url": "https://foo1/bar2”,
"voted": false,
"purchased": false
},
{
"id": 204996257,
"image_url": "https://foo2/bar2”,
"voted": false,
"purchased": false
}
],
...
...
}
So, how let new field can compatible my already existing code?
Optional Property? (I not know how to do this.)
Or add a subclass of inherit from PhotoListResponseModel & PhotoModel?
Or any good idea : )
Update: I noticed this information,but still don't know how to do it.
Optional properties are only available in Swift, but similar idea for the Obj-C — objects with possible nil value — should work well. You can add the following NSNumber properties to your PhotoModel:
#property (nonatomic, strong) NSNumber *voted;
#property (nonatomic, strong) NSNumber *purchased;
And assign your BOOL values from parsed JSON during PhotoModel object creation like this:
photoModelObject.voted = [NSNumber numberWithBool:[parsedJson valueForKey:#"voted"]];
photoModelObject.purchased = [NSNumber numberWithBool:[parsedJson valueForKey:#"purchased"]];
Next, when you reference your photo object, just check if these properties have nil value. If they are — your user is not logged in and you don't need to show them. Otherwise, extract BOOL value from them like this:
if (photoModelObject.voted && photoMobelObject.purchased) {
BOOL isVoted = [photoModelObject.voted boolValue];
BOOL isPurchased = [photoModelObject.purchased boolValue];
// Use booleans to present info for registered users
} else {
// There is no info about votes and purchasing provided for the current user
}

Rest Kit core data over writing master data during GET store of entity calls

I have an entity called Currency and Another entity called Order which has a relationship of one to one with Currency. The currency is mapped with an identification attribute code like this:
l_mapping.identificationAttributes = #[#"code"];
Now when Order call is successful the currency relationship in the Order with same code say USD over writes the one in the Currency table. How can i avoid this? is there a way to just reference it and not create/overwrite the data in Currency table. Any help is much appreciated.
Here is a cut down version of the JSON and model classes.
#class Order;
#interface Currency : NSManagedObject
#property (nonatomic,retain) NSNumber * version;
#property (nonatomic, retain) NSString * code;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) Order *billCurrencyInv;
#end
RKEntityMapping* Mapping = [RKEntityMapping mappingForEntityForName:#"Currency" inManagedObjectStore:managedObjectStore];
Mapping.identificationAttributes = #[ #"code" ];
[Mapping addAttributeMappingsFromDictionary:#{
#"code": #"code",
#"version": #"version",
#"name": #"name"
}];
{
 "List": [
    {
     "code": "GBP",
     "version": 1,
     "name": "Pound"
    },
    {
     "code": "USD",
     "version": 1,
     "name": "US Dollars"
    }
 ],
 "totalCount": 2
}
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Currency;
#interface Order : NSManagedObject
#property (nonatomic,retain) NSNumber * version;
#property (nonatomic, retain) NSString * createdBy;
#property (nonatomic, retain) NSString * type;
#property (nonatomic, retain) Currency * billCurrency;
#end
RKEntityMapping* orderMapping = [RKEntityMapping mappingForEntityForName:#"Order" inManagedObjectStore:managedObjectStore];
orderMapping.identificationAttributes = #[ #"type" ];
[orderMapping addAttributeMappingsFromDictionary:#{
#"type": #"type",
#"createdBy": #"createdBy",
#"version": #"version"
}];
[orderMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"billCurrency"
toKeyPath:#"billCurrency"
withMapping:Mapping]];
{
 "List": [
    {
     "type": "Order”,
     "createdBy": "Me",
     "version": 1,
     "billCurrency": {
       "code": "GBP",
       "name": "British Pound"
     },
],
}
What happens is that the currency with GBP name as "Pound" is overwritten as "British - Pound". Which i dont want. I would like know if there is a way to let rest kit know that it has to reference the currency object of GBP and not create or overwrite it when making a core data object of order. I have tried will all types of relationships(order - currency) ie one-to-one, many-to-one. But to no use. if i set bill currency to transient it just does not have any data in Order which i guess is the right thing to happen. Thanks in advance.

RestKit 0.2 'NSInternalInconsistencyException', reason: 'Unable to add mapping for keyPath scheduleEntries, one already exists...'

I need some help configuring RestKit 0.2x... I am returning JSON with a Schedule object and ScheduleEntries as a nested array.
Sample JSON:
[{
"id": 3,
"name": "Schedule 9",
"sWeek": 9,
"sYear": 2014,
"companyId": 1,
"createdOn": "2014-02-11T22:01:33.547",
"modifiedOn": null,
"companyCode": "0001",
"isActive": true,
"notes": "This is a test note",
"scheduleEntries": [{
"id": 15,
"companyId": 1,
"userId": "a7e46520-8db7-4452-821d-7938b23fcd07",
"scheduleId": 3,
"jobId": 5,
"isActive": true,
"createdOn": "2014-02-11T22:01:33.993",
"modifiedOn": null,
"sWeek": 11,
"sYear": 2014,
"startTime": "2014-03-10T08:48:00",
"endTime": "2014-03-10T08:48:00"
}, {
"id": 16,
"companyId": 1,
"userId": "a7e46520-8db7-4452-821d-7938b23fcd07",
"scheduleId": 3,
"jobId": 3,
"isActive": true,
"createdOn": "2014-02-11T22:01:34.003",
"modifiedOn": null,
"sWeek": 11,
"sYear": 2014,
"startTime": "2014-03-11T00:57:00",
"endTime": "2014-03-11T00:57:00"
}, {....
NSManagedObjects
#interface Schedule : NSManagedObject
#property (nonatomic, retain) NSNumber * scheduleId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSNumber * sWeek;
#property (nonatomic, retain) NSNumber * sYear;
#property (nonatomic, retain) NSNumber * companyId;
#property (nonatomic, retain) NSString * companyCode;
#property (nonatomic, retain) NSString * notes;
#property (nonatomic, retain) NSNumber * isActive;
#property (nonatomic, retain) NSDate * createdOn;
#property (nonatomic, retain) NSDate * modifiedOn;
#property (nonatomic, retain) NSSet * scheduleEntries;
#end
#implementation Schedule
#dynamic scheduleId;
#dynamic name;
#dynamic sWeek;
#dynamic sYear;
#dynamic companyId;
#dynamic companyCode;
#dynamic notes;
#dynamic isActive;
#dynamic createdOn;
#dynamic modifiedOn;
#dynamic scheduleEntries;
#end
#interface ScheduleEntry : NSManagedObject
#property (nonatomic, retain) NSNumber * scheduleEntryId;
#property (nonatomic, retain) NSNumber * companyId;
#property (nonatomic, retain) NSNumber * usrId;
#property (nonatomic, retain) NSNumber * scheduleId;
#property (nonatomic, retain) NSNumber * jobId;
#property (nonatomic, retain) NSNumber * sWeek;
#property (nonatomic, retain) NSNumber * sYear;
#property (nonatomic, retain) NSDate * startTime;
#property (nonatomic, retain) NSDate * endTime;
#property (nonatomic, retain) NSNumber * isActive;
#property (nonatomic, retain) NSDate * createdOn;
#property (nonatomic, retain) NSDate * modifiedOn;
#end
#implementation ScheduleEntry
#dynamic scheduleEntryId;
#dynamic companyId;
#dynamic usrId;
#dynamic scheduleId;
#dynamic jobId;
#dynamic sWeek;
#dynamic sYear;
#dynamic startTime;
#dynamic endTime;
#dynamic isActive;
#dynamic createdOn;
#dynamic modifiedOn;
#end
Code:
RKManagedObjectStore *managedObjectStore = [RKManagedObjectStore defaultStore];
// Create mapping for entity
RKEntityMapping *scheduleEntryMapping = [RKEntityMapping mappingForEntityForName:#"ScheduleEntry" inManagedObjectStore:managedObjectStore];
scheduleEntryMapping.identificationAttributes = #[#"scheduleEntryId"];
[scheduleEntryMapping addAttributeMappingsFromDictionary:#{
#"id" : #"scheduleEntryId",
#"companyId": #"companyId",
#"usrId" : #"usrId",
#"scheduleId" : #"scheduleId",
#"jobId" : #"jobId",
#"isActive": #"isActive",
#"sWeek": #"sWeek",
#"sYear": #"sYear",
#"startTime": #"startTime",
#"endTime": #"endTime",
#"createdOn": #"createdOn",
#"modifiedOn": #"modifiedOn"
}];
RKEntityMapping *scheduleMapping = [RKEntityMapping mappingForEntityForName:#"Schedule" inManagedObjectStore:managedObjectStore];
scheduleMapping.identificationAttributes = #[#"scheduleId"];
[scheduleMapping addAttributeMappingsFromDictionary:#{
#"id" : #"scheduleId",
#"name": #"name",
#"sWeek": #"sWeek",
#"sYear": #"sYear",
#"companyId": #"companyId",
#"companyCode": #"companyCode",
#"notes": #"notes",
#"isActive": #"isActive",
#"createdOn": #"createdOn",
#"modifiedOn": #"modifiedOn",
#"scheduleEntries": #"scheduleEntries"
}];
[scheduleMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"scheduleEntries"
toKeyPath:#"scheduleEntries"
withMapping:scheduleEntryMapping]];
This code is very close to the RestKit 0.2x example but I can't seem to get it to work. I can get a single object just fine, but when it comes to objects that have nested data I'm stuck. It has been a few days working on this with no results. The error says I'm setting the keyPath more than once but I don't see it. Any help would be appreciated.
You just need to remove #"scheduleEntries": #"scheduleEntries" from the mapping dictionary on scheduleMapping because that is added (and therefore duplicated) by the relationship mapping.
Other than that things look correct.

Resources