RestKit 0.20 POST JSON with nested array - restkit

I have an object called Client which looks something like this.
#interface Client : NSManagedObject
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * middleName;
#property (nonatomic, retain) NSString * lastName;
#property (nonatomic, retain) Styles *clientStyles;
#end
Styles is a nested object underneath Client. It's a One to One relationship. When this comes down from the server in JSON it looks like this.
{
"firstName": "",
"middleName": "",
"lastName": "",
"firstStyle": {
"styleId": 4,
"name": "",
"description": "",
"stylingTime": "55 min",
"stylingProductUsage": "A lot",
"chemicals": "LOTS O'GEL",
"deleted": false,
"modifiedOn": 1357161168830
}
}
Everything is in a nice single object. I can pull this down and map it to my object no problem. The issue comes in when I need to return this back to the server. It needs to be in this format.
{
"firstName": "",
"middleName": "",
"lastName": "",
"styles": [
{
"styleId": 4,
"name": "",
"description": "",
"stylingTime": "55 min",
"stylingProductUsage": "A lot",
"chemicals": "LOTS O'GEL",
"deleted": false,
"modifiedOn": 1357161168830
}]
}
Which is very problematic because the return mapping has the styles entity sitting inside an array, rather then being a one-to-one. So far I have got this as my RKRequestDescriptor
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromDictionary:#{
#"firstName": #"firstName",
#"middleName": #"middleName",
#"lastName": #"lastName",
}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:[Client class]
rootKeyPath:nil];
How the HECK do I create the mapping so it will return an array of Style objects with one value???

Wild guess, but the mappings are pretty smart, can't you do something like:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromDictionary:#{
#"firstName": #"firstName",
#"middleName": #"middleName",
#"lastName": #"lastName",
}];
RKObjectMapping *stylesMappingDescription = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromDictionary:#{
#"properties": #"here"
}];
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"styles.0" toKeyPath:#"styles" withMapping:stylesMappingDescription]];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping
objectClass:[Client class]
rootKeyPath:nil];
(note the styles.0 as fromKeyPath)

Related

Storing image path in plist then retrieve to show on screen

I am trying to make an app where the picture comes up with the anagram of a word. I have the anagram part but I can't get the picture to come up. The picture is saved in a folder called "images". I would like to call anagramPics - item 0, at the same time as anagrams -item 0 - to match the word with the pic
Thanks in advance.
code for the anagram:
level.m
#import "Level.h"
#implementation Level
+(instancetype)levelWithNum:(int)levelNum;
{
// find .plist file for this level
NSString* fileName = [NSString stringWithFormat:#"level%i.plist", levelNum];
NSString* levelPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
// load .plist file
NSDictionary* levelDict = [NSDictionary dictionaryWithContentsOfFile:levelPath];
// validation
NSAssert(levelDict, #"level config file not found");
// create Level instance
Level* l = [[Level alloc] init];
// initialize the object from the dictionary
l.pointsPerTile = [levelDict[#"pointsPerTile"] intValue];
l.anagrams = levelDict[#"anagrams"];
l.timeToSolve = [levelDict[#"timeToSolve"] intValue];
return l;
}
#end
level.h
#import <Foundation/Foundation.h>
#interface Level : NSObject
//properties stored in a .plist file
#property (assign, nonatomic) int pointsPerTile;
#property (assign, nonatomic) int timeToSolve;
#property (strong, nonatomic) NSArray* anagrams;
//factory method to load a .plist file and initialize the model
+(instancetype)levelWithNum:(int)levelNum;
#end
gameController.h
-(void)dealRandomAnagram
{
//1
NSAssert(self.level.anagrams, #"no level loaded");
//2 random anagram
int randomIndex = arc4random()%[self.level.anagrams count];
NSArray* anaPair = self.level.anagrams[ randomIndex ];
//3
NSString* anagram1 = anaPair[0];
NSString* anagram2 = anaPair[1];
//4
int ana1len = [anagram1 length];
int ana2len = [anagram2 length];
//5
NSLog(#"phrase1[%i]: %#", ana1len, anagram1);
NSLog(#"phrase2[%i]: %#", ana2len, anagram2);
}
I have a similar situation where I have some fixed images to display. This is what I did.
Added a UIImageView on Storyboard. Positioned it out where I wanted it using constraints etc.
Created an IBOutlet for it. Call it myImageView.
I added all my images as assets. In my case I'm displaying various credit card images but the principle is the same.
Each image asset is a folder with a Contents.json file and the image files that go with it. You will note that there are three files. iOS will use the correct size image for retina, iPhone 6plus etc.
Contents.json looks like this:
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "card-visa#1x.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "card-visa#2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "card-visa#3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
In the code, change the image of the UIViewImage by setting its image property.
(This is Swift code. You will have to convert it to Objective-C yourself.)
myImageView.image = UIImage(named:"Card VISA")

RestKit KVC Validation won't call validation methods

this is my first question here :)
OK so I have a projet with ReskKit 0.23.3 via cocoapods. I use RestKit/CoreData.
I fetch an URL, the result got mapped to my object and is correctly saved by core data. I want to use Key-Value Validation to check some values retrieved against the one already persisted. I read that i could use the methods validateKey:error: on my NSManagedObject. Somehow, it is never called. I'm frustrated...
Here are my files (for simplicity, i concatenated logic code into one flat file here):
JSON response /collections/{id}
{
"id": "00000000-0000-0000-0000-00000000000",
"image_url": "http://server/image.png",
"name": "Collection C",
"etag": 42,
"ctag": 42
}
Collection.h
#interface Collection : NSManagedObject
#property(nonatomic, strong) NSString *collectionId;
#property(nonatomic, strong) NSString *name;
#property(nonatomic, strong) NSURL *imageUrl;
#property(nonatomic, strong) NSNumber *etag;
#property(nonatomic, strong) NSNumber *ctag;
#end
Collection.m
#implementation Collection
#dynamic collectionId, name, imageUrl, etag, ctag;
- (BOOL)validateCollectionId:(id *)ioValue error:(NSError **)outError {
NSLog(#"Validating id");
NSLog(#"Coredata collection id: %#", self.collectionId);
NSLog(#"GET collection id: %#", (NSString *)*ioValue);
return YES;
}
- (BOOL)validateEtag:(id *)ioValue error:(NSError **)outError {
NSLog(#"Validating etag");
NSLog(#"Coredata collection etag: %#", self.etag);
NSLog(#"GET collection etag: %#", (NSString *)*ioValue);
return YES;
}
#end
Code
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
[managedObjectStore createPersistentStoreCoordinator];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingString:#"/MyApp.sqlite"];
NSError *error;
NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
[managedObjectStore createManagedObjectContexts];
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[RKManagedObjectStore setDefaultStore:managedObjectStore];
NSURL *url = [NSURL URLWithString:#"http://server/api"];
RKObjectManager *objectManager = [self managerWithBaseURL:url];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
objectManager.managedObjectStore = [RKManagedObjectStore defaultStore];
RKEntityMapping *collectionMapping = [RKEntityMapping mappingForEntityForName:#"Collection" inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[collectionMapping addAttributeMappingsFromDictionary:#{#"id": #"collectionId",
#"image_url": #"imageUrl"}];
[collectionMapping addAttributeMappingsFromArray:#[#"name", #"etag", #"ctag"]];
[collectionMapping setIdentificationAttributes:#[#"collectionId"]];
RKResponseDescriptor *collectionResponseDescriptors = [RKResponseDescriptor responseDescriptorWithMapping:collectionMapping
method:RKRequestMethodGET pathPattern:#"collections/:collectionId"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:collectionResponseDescriptor];
[objectManager getObjectsAtPath:#"collections/00000000-0000-0000-0000-00000000000" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
Collection *collection = (Collection *)[mappingResult.array firstObject];
NSLog(#"Collection: %#", collection);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Oh noes :(");
}];
Output
2014-09-13 12:39:04.242 MyApp[41958:607] I restkit:RKLog.m:33 RestKit logging initialized...
2014-09-13 12:39:05.028 MyApp[41890:607] Collection: <NSManagedObject: 0x9108a60> (entity: Collection; id: 0x94166d0 <x-coredata://6645F428-7631-45F0-A8AF-E2352C50F35E/Collection/p1> ; data: {
collectionId = "00000000-0000-0000-0000-00000000000";
ctag = 42;
etag = 42;
imageUrl = "http://server/image.png";
name = "Collection C";
})
So, I get my Log with the Collection, but None of the NSLog in the validate<Key>:error: methods got triggered... Could not figure out why!
Edit
With some breaks, i figured this is RKMappingOperation who is responsible for calling those validation methods on my object. Precisely it's validateValue:atKeyPath:
RKMappingOperation.m
...
- (BOOL)validateValue:(id *)value atKeyPath:(NSString *)keyPath
{
BOOL success = YES;
if (self.objectMapping.performsKeyValueValidation && [self.destinationObject respondsToSelector:#selector(validateValue:forKeyPath:error:)]) {
NSError *validationError;
success = [self.destinationObject validateValue:value forKeyPath:keyPath error:&validationError];
...
}
...
But self.destinationObject is an NSManagedObject and not a Collection object...
Console
(lldb) po [self.destinationObject class]
NSManagedObject
I hope you could lead me to the right way :) Thank you!
It appears that you have not specified that the entity should use the Collection class in the Core Data model. If you don't specify anything then NSManagedObject will be used by default.

Magical Record exception while save One-To-Many Entity

in my new App I have Core Data and Magical Record and this is how is structured the db:
This is the class corresponding to the entity NEWS:
#class SMCategories;
#interface SMNews : NSManagedObject
#property (nonatomic, retain) NSNumber * wpId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * content;
#property (nonatomic, retain) NSString * url;
#property (nonatomic, retain) NSString * thumbnailUrl;
#property (nonatomic, retain) NSString * thumbnailFile;
#property (nonatomic, retain) NSDate * date;
#property (nonatomic, retain) SMCategories *category;
This is the class corresponding to the entity CATEGORIES:
#interface SMCategories : NSManagedObject
#property (nonatomic, retain) NSNumber * wpId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSDate * lastUpdate;
#property (nonatomic, retain) NSSet *news;
#end
#interface SMCategories (CoreDataGeneratedAccessors)
- (void)addNewsObject:(NSManagedObject *)value;
- (void)removeNewsObject:(NSManagedObject *)value;
- (void)addNews:(NSSet *)values;
- (void)removeNews:(NSSet *)values;
This is the function I use to save, inside a cycle, all the datas that should be saved in the entity CATEGORIES:
- (void)saveUpdateCategories:(NSString *)result {
NSDictionary *tmpCategories = [result objectFromJSONString];
NSArray *categoriesList = [tmpCategories objectForKey:#"categories"];
NSDictionary *singleCategory;
NSPredicate * filter = [[NSPredicate alloc] init];
for(int i = 0; i < [categoriesList count];i++) {
singleCategory = [categoriesList objectAtIndex:i];
filter = [NSPredicate predicateWithFormat:#"wpId == %#",[singleCategory objectForKey:#"id"]];
SMCategories *checkCategory = [SMCategories MR_findFirstWithPredicate:filter];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
SMCategories *categoryToAdd;
if (checkCategory == nil) {
categoryToAdd = [SMCategories MR_createEntityInContext: localContext];
} else {
categoryToAdd = checkCategory;
}
categoryToAdd.name = [singleCategory objectForKey:#"name"];
categoryToAdd.wpId = [currentUtils stringToNumber:[singleCategory objectForKey:#"id"]];
categoryToAdd.lastUpdate = [currentUtils stringToDate:#"01/01/2000 00:00:01" formatted:#"dd/MM/yyyy HH:mm:ss"];
categoryToAdd.news = nil;
} completion:^(BOOL success, NSError *error) {
if (i == [categoriesList count] - 1) {
[progressWheel stopAnimating];
NSMutableDictionary *tmpOptions = [[NSMutableDictionary alloc] init];
tmpOptions = [currentUtils getDictionaryPlistFile:#"options.plist" path:[currentUtils getMainLocalPath]];
[tmpOptions setObject:[NSDate date] forKey:#"lastCategoriesUpdates"];
[currentUtils saveDictionaryPlistFile:tmpOptions fileName:#"options.plist" path:[currentUtils getMainLocalPath]];
[menuButton setEnabled:YES];
if (categoryToLoadPassed == nil)
categoryToLoadPassed = [SMCategories MR_findFirstByAttribute:#"name" withValue:DEFAULT_CATEGORY];
[self downloadNewsOfCategory:categoryToLoadPassed];
}
}];
}
}
This part of code works as well.
This is the funcion I use to save, inside another cycle, all the datas that should be inserted into the entity NEWS.
-(void) saveNewsOfCategory:(SMCategories *)category resultToSave: (NSString *) result {
NSDictionary *tmpNews = [result objectFromJSONString];
NSArray *newsList = [tmpNews objectForKey:#"posts"];
NSDictionary *singleNew;
NSPredicate * filter = [[NSPredicate alloc] init];
for(int i = 0; i < [newsList count];i++) {
singleNew = [newsList objectAtIndex:i];
filter = [NSPredicate predicateWithFormat:#"wpId == %#",[singleNew objectForKey:#"id"]];
SMNews *checkNew = [SMNews MR_findFirstWithPredicate:filter];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
SMNews *newToAdd;
if (checkNew == nil) {
newToAdd = [SMNews MR_createEntityInContext: localContext];
} else {
newToAdd = checkNew;
}
newToAdd.category = category;
newToAdd.title = [singleNew objectForKey:#"title"];
newToAdd.content = [singleNew objectForKey:#"content"];
newToAdd.wpId = [currentUtils stringToNumber:[singleNew objectForKey:#"id"]];
newToAdd.date = [currentUtils stringToDate:[singleNew objectForKey:#"date"] formatted:#"yyyy-MM-dd HH:mm:ss"];
newToAdd.url = [NSString stringWithFormat:#"%#?p=%#",WEBSITE,[singleNew objectForKey:#"id"]];
newToAdd.thumbnailFile = nil;
newToAdd.thumbnailUrl = [singleNew objectForKey:#"image"];
} completion:^(BOOL success, NSError *error) {
if (i == [newsList count] - 1) {
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
category.lastUpdate = [NSDate date];
} completion:^(BOOL success, NSError *error) {
[progressWheel stopAnimating];
[currentTable reloadData];
}];
}
}];
}
}
Here it's thrown an exception EXT_BAD_ACCESS next to the line
newToAdd.category = category;
category is passed as one of the parameters of the corresponding function.
As you would have understood I have 2 entitis CATEGORIES and NEWS with a One-To-Many relationship.
First off I download all the categories and I store them into CATEGORIES entity when the user wants to download all the news of a specific category I download them and store with the second function into news entity passign into the second function the category of those specific news as parameters of my function.
If delete the line incriminated all the news are correctly store inside the database but they are not related to any cateogries.
What am I doing wrong?
The following line that threw an exception because I save the entity in a new Thread.
newToAdd.category = category;
So used a singleton class to get the category and set in that specific localcontex this way
SMCategories *currentCategory = [[DataManager getInstance] getCurrentCategory];
newsToAdd.category = [currentCategory MR_inContext:localContext];
Now everything works correctly.

How does RestKit map a out key to a model?

This is my json and managed object model:
{
lasttime: 1387351751288
AreaList: [
{
provinceid: 1,
provincename: "a",
count: 1,
},
{
provinceid: 2,
provincename: "b",
count: 2,
}
]
}
#interface Province : NSManagedObject
#property (nonatomic, retain) NSString * provinceid;
#property (nonatomic, retain) NSString * provincename;
#property (nonatomic, retain) NSNumber * count;
#property (nonatomic, retain) NSNumber * lasttime;
#end
I want to map the "lasttime" into the Province.lasttime, how should I modify the mapping?
To copy direct into the object can not be done during the mapping because you can't both index into the array and use things outside the array at the same time.
I would consider mapping the time into its own object and then connect via a relationship with the provinces. Again, this can't be done entirely in the mapping for the same reason, so you would need to connect the objects in the success block.
Using the success block to make the connection would work just as well without the relationship if you map the time into an object and then copy the value across.
Try this:
[mapping addAttributeMappingsFromDictionary:#{
#"#parent.lasttime" : #"lasttime"
}];

RestKit Mapping Error "Cannot map a collection of objects onto a non-mutable collection."

Okay, so I have this code that I'm using to map data from a JSON API. Everything runs fine, but then I get this error:
restkit.object_mapping:RKMapperOperation.m:76 Adding mapping error: Cannot map a collection of objects onto a non-mutable collection. Unexpected destination object type '__NSDictionaryI'
Here is the code I'm using for the mapping
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
RKObjectMapping *classMapping = [RKObjectMapping mappingForClass:[GradePeriod class]];
[classMapping addAttributeMappingsFromDictionary: #{
#"period" : #"period",
#"class" : #"name",
#"term" : #"term",
#"teacher" : #"teacher",
#"percent" : #"percent",
#"grade" : #"grade",
#"missing" : #"missing",
#"update" : #"update",
#"detail" : #"url"
}];
// Create a Response Descriptor
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:classMapping
pathPattern: nil
keyPath: #"grades"
statusCodes:[NSIndexSet indexSetWithIndex:200]];
[objectManager addResponseDescriptor:responseDescriptor];
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
NSDictionary *queryParams;
queryParams = [NSDictionary dictionaryWithObjectsAndKeys:username, #"username", password, #"password", nil];
// Retrieve the Data
[objectManager postObject:queryParams
path:#""
parameters:queryParams
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
grades = [NSMutableArray arrayWithArray: [mappingResult array]];
NSLog(#"Loaded Grades: %#", grades);
[self.tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Stuff removed for easier reading
}];
Grades is an NSMutableArray
#property (nonatomic, retain) NSMutableArray *grades;
GradePeriod is defined like this
#property (strong, nonatomic) NSString *period;
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSString *term;
#property (strong, nonatomic) NSString *teacher;
#property (strong, nonatomic) NSString *percent;
#property (strong, nonatomic) NSString *grade;
#property (strong, nonatomic) NSString *missing;
#property (strong, nonatomic) NSString *update;
#property (strong, nonatomic) NSString *url;
I'm not sure what I'm doing wrong.
Thanks so much!
I upgraded to the latest version of RestKit and it fixed itself.

Resources