11

I'm using RestKit and i want to parse and save elements to core data. I have two json files:

First (Category):

[
   {
     "cat_id": 3371,
     "cat_name": "myName",
     "image": 762
   },
   {
     "cat_id": 3367,
     "cat_name": "anotherName",
     "image": 617
   }
]

And second (Elements):

[
  {
    "art_id": "1",
    "node": {
      "author": "name"
    },
    "small_url": 0
  },
  {
    "art_id": "12",
    "node": {
      "author": "anotherName"
    },
    "small_url": 0
  }
]

So the basic idea is that every category have some elements inside. So this is my CoreData struct: enter image description here

I've download the restkit example and use TwitterCoreData sample. My code is: AppDelegeta.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSURL *baseURL = [NSURL URLWithString:@"http://globalURL.com"];
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseURL];
    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
    objectManager.managedObjectStore = managedObjectStore;

    RKEntityMapping *categoryMapping = [RKEntityMapping mappingForEntityForName:@"Category" inManagedObjectStore:managedObjectStore];
    categoryMapping.identificationAttributes = @[ @"catId" ];
    [categoryMapping addAttributeMappingsFromDictionary:@{
     @"cat_id": @"catId",
     @"node.author": @"author",
     }];

    RKEntityMapping *elementsMapping = [RKEntityMapping mappingForEntityForName:@"Elements" inManagedObjectStore:managedObjectStore];
    elementsMapping.identificationAttributes = @[ @"artId" ];
    [elementsMapping addAttributeMappingsFromDictionary:@{
     @"art_id": @"artId",
     @"node.author": @"author",
    }];
    [elementsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"category" toKeyPath:@"category" withMapping:categoryMapping]];
    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:elementsMapping
                                                                                       pathPattern:nil
                                                                                           keyPath:nil
                                                                                       statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    [objectManager addResponseDescriptor:responseDescriptor];


    [managedObjectStore createPersistentStoreCoordinator];
    NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"MyCoreData.sqlite"];
    NSString *seedPath = [[NSBundle mainBundle] pathForResource:@"MyCoreData" ofType:@"sqlite"];
    NSError *error;
    NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error];
    NSAssert(persistentStore, @"Failed to add persistent store with error: %@", error);

    // Create the managed object contexts
    [managedObjectStore createManagedObjectContexts];

    // Configure a managed object cache to ensure we do not create duplicate objects
    managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];



    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

and ViewController.m:

NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Elements"];
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"artId" ascending:NO];
fetchRequest.sortDescriptors = @[descriptor];
[[RKObjectManager sharedManager] getObjectsAtPath:@"/detailaddress/:catId" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
    RKLogInfo(@"Load complete: Table should refresh...");
    NSLog(@"%@",mappingResult);
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"LastUpdatedAt"];
    [[NSUserDefaults standardUserDefaults] synchronize];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
    RKLogError(@"Load failed with error: %@", error);
}];

And log for mapping show me the "nil". How to save data from my first json (category) into core data using restkit? Rememnber that i don't have Elements list yet.

When i use create new file to create NEManagedObject subclass i've got Elements class.

@interface Elements : NSManagedObject

@property (nonatomic, retain) NSNumber * artId;
@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSManagedObject *category;

@end
Jakub
  • 13,712
  • 17
  • 82
  • 139

2 Answers2

41

I used RESTKit until the .20 release a few months ago. In all honesty, while this library has great intentions, in my experience it ends up wasting far more time than it was ever meant to save me.

I have used it with API's created with .NET/MVC, PHP, python/Django/TastyPie, and hacked it to do some stuff with Twitter. This all ended up being a very interesting academic exercise, but in the end was little more than that. Both the old/new versions of RESTKit assume ALOT about the way your API is going to be responding to requests.

With the .10 release, there was a fair amount of customizability through the use of Obj-C Blocks. I could intercept the request/response of an RKRequest and pretty much override anything that RESTKit was going to do. This seemed fairly awesome. Now with the .20 release, everything got very... clever (clever code is usually not good).

Instead of trying to remain generic and malleable, RK now does all kinds of "convenient" things for you. This effectively takes away your ability to jam it into whatever (non-idealized and realistic) shape your API has taken. At this point, I have gone back to writing all of my code using AFNetworking. I'd rather take the extra time to write my own parsing code, and know that when my Unit Tests pass, I'm done. NOT spend hours trying to get RK to log out meaningful error messages.

The other real problem with RK was that it does not account for being offline. It was designed with the assumption that your application is ALWAYS online, and simply mirrors a persistant store in the sky. My applications all handle content-creation on an iOS device, which MAY OR MAY NOT be online at the time of content-creation. This is the main reason I was "customizing" RK .10. When I saw the changes in .20, I decided enough was enough, and went back to doing things the old way.

Maybe I'll just write my own CRUD/RESTful framework, cause I'm starting to get tired of spending time using very specialized libraries, that try to do everything with as little responsibility being left to the application developer as possible. Frameworks like this tend to be Swiss Army Knives. They look really awesome on paper, THEY CAN DO ANYTHING, then you actually try to cut something and the blade is too dull or breaks off. Good software does very few things very well, and if it sounds too good to be true, it probably is.

G. Shearer
  • 2,175
  • 17
  • 19
  • 6
    I understand you point of view. This would deserve some debate. As an Objective-C beginner (coming from python world), I admit I haven't yet been able to do anything with RestKit. BUT trying to be the devil's advocate, we can skip whatever suggested RK's _smart layer_ we want and it seems to be designed also for offline content with his [CoreData integration](https://github.com/RestKit/RestKit/wiki/Object-mapping#core-data) isn't it ? I naïvely tend to think that these upvotes are coming more from people discouraged by the complexity than from people understanding the whole stack. – Pierre de LESPINAY May 21 '13 at 14:50
  • 1
    As a python programmer, perhaps you are familiar with Django? Django is a pretty large framework, but it is EASY and very DRY. I love Django, so at least for me personally, the size of RESTKit has little to do with my issue. I have an issue with the overcomplicated design. I built my first Django App in about an hour (with a working ORM). I built my first RESTKit service (on a real API) in just over 4 hours. And RESTKit is really a toolkit for one kind of problem, while Django is a full application framework. I'm talking about the simplicity of good design. – G. Shearer May 21 '13 at 19:53
  • 1
    And I should really say, my first production RESTKit service took about 4 hours to get ANYTHING working. I worked on getting 6 different endpoints working with no mapping issues for over 2 weeks. Thats too much, If I were smart I would have just written my own client like I have in all my other software before RK. – G. Shearer May 21 '13 at 19:55
  • I'm indeed a Djangoist too (my favorite framework by far). I try to avoid comparison between Django and Xcode or Python and Objective-C because I would be discouraged to quickly :) I did noticed that I spend about 20x more time to do every little thing in Objective-C than in Python. And I think that RK sadly inherits from this verbosity and heaviness. Maybe RK is not as much customizable as it could but it seems also largely disadvantaged by the supporting language and Apple standards... – Pierre de LESPINAY May 22 '13 at 07:20
  • 1
    Indeed, please don't let what I've said steer you away from Objective-C... its awesome lol. The verbosity of ObjC is made up for by the fact that when well-written, which most is, it is incredibly readable. Cocoa in general is an amazing example of strict design guidelines and a well thought out framework. Xcode also makes up for the verbosity with autocompletion. In Python land I am a ViM guy, but I would only use Xcode for ObjC – G. Shearer May 22 '13 at 19:50
  • The hypercritical structure of restkit is comparable to ruby, if you know what you are doing and you understand the architecture and limitations, then programming can be quick and efficient but jumping straight into restkit with out reading up on it can make you waste a lot of time. At the same time, the dynamic nature of objective - c, forces restkit to keep up to date, which means adding new content and removing deprecated content which could break old projects and force you to learn more about how the framework is used. – A'sa Dickens Jan 16 '14 at 13:17
  • so when you are writng CRUD/RESTful framework? i am with you if you want to develop – Noor May 22 '15 at 12:51
3

So you've created the mapping for each NSManagedObject (and they work), but now you want to establish a relationship between "Category" and "Elements".

The problem is that there isn't an information for the mapping of Elements in which Category each element belongs (the unique key is missing). So you need to add the cat_id to the Elements JSON to ensure that this keypath is available.

// Elements JSON
[
  {
    "cat_id": "1313",
    "art_id": "1",
    "node": {
      "author": "name"
    },
    "small_url": 0
  },
  ...
]
// Edit keypath in relationship mapping
[elementsMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"cat_id" toKeyPath:@"category" withMapping:categoryMapping]];

In addition you could use the following to methods to track if the mapping works.

1) -com.apple.CoreData.SQLDebug 1 // Add in "Arguments" Tab > "Arguments Passed On Launch" 
2) RKLogConfigureByName("RestKit/*", RKLogLevelTrace); // Add to AppDelegate`

Edit

Mapping

Did you update the entity Elements in your Data Model and your NSManagedObject with the given value? Seems like you didn't.

@interface Elements : NSManagedObject

@property (nonatomic, retain) NSNumber * catId;
@property (nonatomic, retain) NSNumber * artId;
@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSManagedObject *category;

@end

Routing

The path your using for your get request won't work because @"/detailaddress/:catId" isn't a valid path. You can on the one hand set the routing of the object manager in general or define a response description.

 // Route for get operation
 [[RKObjectManager sharedManager].router.routeSet addRoute:[RKRoute 
      routeWithClass:[Category class] 
      pathPattern:@"/detailaddress/:catId" 
      method:RKRequestMethodGET]];

 // Response descriptor taken from the Core Data same application 
 RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:categoryMapping pathPattern:@"/detailaddress/:catId" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];     
 [objectManager addResponseDescriptor:responseDescriptor];

 // Use general path for get request
 [[RKObjectManager sharedManager] getObjectsAtPath:@"/detailaddress" ...

But please check if the mapping and data model is set like expected first.

flashfabrixx
  • 1,183
  • 9
  • 22
  • Why i should do add this extra field? I dont really like it. I'm pretty sure i should use routing to do that but don't know excatly how. – Jakub Feb 19 '13 at 14:48
  • How should the mapping know which object (given by the cat_id) should be the parent of the child your inserting? To verify my answer have a look at the wiki (paragraph "Relationships") https://github.com/RestKit/RestKit/wiki/Object-Mapping – flashfabrixx Feb 20 '13 at 13:12
  • Becouse i pass arg in link. :catId that's why i think about routing. – Jakub Feb 20 '13 at 13:37
  • Even if I add your line to code and edit JSON didn't work. I can't set Category. Always want to create Elements. – Jakub Feb 20 '13 at 14:10
  • 1
    When i do this i get: `*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFNumber 0x9518170> valueForUndefinedKey:]: this class is not key value coding-compliant for the key catId.'` – Jakub Feb 20 '13 at 15:57