RestKit save object manually - objective-c

I had spent few hours try to insert/add object but duplicated records happened. Campaign record re-inserted even it already exists. Am I missing something?
Below are my codes:
Campaign.h
#interface Campaign : NSManagedObject
#property (nonatomic, strong) NSNumber* campaignId;
#property (nonatomic, strong) NSString* title;
#end
Card.h
#class Campaign;
#interface Card : NSManagedObject
#property (nonatomic, strong) NSNumber* cardId;
#property (nonatomic, strong) NSString* name;
#property (nonatomic, strong) Campaign* campaign;
#end
ViewController.m
...
Campaign* campaign = [Campaign object];
campaign.campaignId = [NSNumber numberWithInt:1];
campaign.title = #"Hello world";
Card* card = [Card object];
card.cardId = #"1234567890";
card.campaign = campaign;
[[[RKObjectManager sharedManager] objectStore] save:nil];
EDITED ViewController.m
...
RKManagedObjectMapping* cardMapping = [RKManagedObjectMapping mappingForClass:[Card class] inManagedObjectStore:[RKObjectManager sharedManager].objectStore];
[cardMapping mapKeyPath:#"id" toAttribute:#"cardId"];
[campaignMapping mapKeyPath:#"name" toAttribute:#"name"];
cardMapping.primaryKeyAttribute = #"cardId";
RKManagedObjectMapping* campaignMapping = [RKManagedObjectMapping mappingForClass:[Campaign class] inManagedObjectStore:[RKObjectManager sharedManager].objectStore];
[campaignMapping mapKeyPath:#"id" toAttribute:#"campaignId"];
[campaignMapping mapKeyPath:#"title" toAttribute:#"title"];
campaignMapping.primaryKeyAttribute = #"campaignId";
Campaign* campaign = [Campaign object];
campaign.campaignId = [NSNumber numberWithInt:1];
campaign.title = #"Hello world";
Card* card = [Card object];
card.cardId = #"1234567890";
card.campaign = campaign;
[[[RKObjectManager sharedManager] objectStore] save:nil];

Yes, add a .primaryKeyAttribute to your mapping.
This will do the pk stuff for you when you are importing data via rest kit. If you are just doing 'normal' core data stuff with Restkit, you need to deal with referential integtrity etc. yourself.

Related

RestKit RKMappingResult gives [__NSCFBoolean length]: unrecognized selector sent to instance

I am fetching data from the New York Times Bestsellers JSON API using Reskit. I believe I have an issue with my attributes mapping. A typical JSON object that has to be fetched looks like the structure below. My code is also shown. The API call does return objects as matching the number of expected results but the RKMappingResult in the requestDataFromAPI method returns "[__NSCFBoolean length]: unrecognized selector sent to instance". I am not able to access them as printing Books gives nil. I am not sure what I'm doing wrong.
{
"status":"OK",
"copyright":"Copyright (c) 2016 The New York Times Company. All Rights Reserved.",
"num_results":10,
"last_modified":"2016-03-04T13:12:31-05:00",
"results":
{
"list_name":"Animals",
"bestsellers_date":"2016-02-27",
"published_date":"2016-03-13",
"display_name":"Animals",
"normal_list_ends_at":10,
"updated":"MONTHLY",
"books": [
{"rank":1,
"rank_last_week":0,
"weeks_on_list":0,
"asterisk":0,
"dagger":0,
"primary_isbn10":"0802123414",
"primary_isbn13":"9780802123411",
"publisher":"Grove Atlantic",
"description":"A grief-stricken British woman decides to raise a goshawk, a fierce bird that is notoriously difficult to tame.",
"price":0,
"title":"H IS FOR HAWK","author":"Helen Macdonald",
"contributor":"by Helen Macdonald",
"isbns": [
{"isbn10":"0802123414",
"isbn13":"9780802123411"
},
{"isbn10":"1448130727",
"isbn13":"9781448130726"
},
{"isbn10":"1481530968",
"isbn13":"9781481530965"
},
{"isbn10":"148153095X",
"isbn13":"9781481530958"
},
{"isbn10":"1410483614",
"isbn13":"9781410483614"
},
{"isbn10":"0802124739",
"isbn13":"9780802124739"
}]
}
}
- (void) initializeRestAPI
{
// Initialize RestKit using API base address
NSURL * baseURL = [NSURL URLWithString:#"http://api.nytimes.com"];
RKObjectManager * objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// Initialize Core Data's managed object model from the bundle
NSManagedObjectModel * managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
// Initialize RestKit's managed object store
RKManagedObjectStore * managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
// Complete Core Data stack initialization via RestKit
[managedObjectStore createPersistentStoreCoordinator];
NSString * persistentStorePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"DataModel.sqlite"];
NSString * seedDatabasePath = [[NSBundle mainBundle] pathForResource:#"RKSeedDatabase" ofType:#"sqlite"];
NSError * error;
NSPersistentStore * persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:persistentStorePath fromSeedDatabaseAtPath:seedDatabasePath withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
// Create RestKit's managed object contexts
[managedObjectStore createManagedObjectContexts];
// Configure a managed object cache
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[self setupEntityMappingForObjectStore:managedObjectStore withObjectManager:objectManager];
[self requestDataFromAPI];
}
-(void) setupEntityMappingForObjectStore: (RKManagedObjectStore *) managedObjectStore withObjectManager: (RKObjectManager *) objectManager
{
RKEntityMapping * bookListMapping = [RKEntityMapping mappingForEntityForName:#"BookList" inManagedObjectStore:managedObjectStore];
bookListMapping.identificationAttributes = #[#"listName"];
[bookListMapping addAttributeMappingsFromDictionary:
#{#"results.list_name": #"listName",
#"results.bestsellers_date": #"bestsellersDate",
#"results.published_date": #"publishedDate",
#"results.display_name": #"displayName",
#"results.normal_list_ends_at": #"normalListEndsAt",
#"results.updated": #"updated"
}];
RKEntityMapping * bookMapping = [RKEntityMapping mappingForEntityForName:#"Book" inManagedObjectStore:managedObjectStore];
bookMapping.identificationAttributes = #[#"title"];
[bookMapping addAttributeMappingsFromDictionary:
#{#"rank": #"rank",
#"rank_last_week": #"rankLastWeek",
#"weeks_on_list": #"weeksOnList",
#"primary_isbn10": #"primaryIsbn10",
#"primary_isbn13": #"primaryIsbn13",
#"amazon_product_url": #"productUrl",
#"book_image": #"bookImage",
#"publisher": #"publisher",
#"description": #"bookDescription",
#"title": #"title",
#"contributor": #"contributor",
#"author": #"author",
#"price": #"price"
}];
[bookListMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"results.books" toKeyPath:#"books" withMapping:bookMapping]];
RKResponseDescriptor * bookListResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:bookListMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"results.books"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
];
[objectManager addResponseDescriptor:bookListResponseDescriptor];
// Enable Activity Indicator Spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
}
- (void)fetchBooksFromContext
{
NSManagedObjectContext * context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"BookList"];
NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey:#"listName" ascending:YES];
fetchRequest.sortDescriptors = #[descriptor];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
BookList * bookList = [fetchedObjects firstObject];
NSArray * books = [bookList.books allObjects];
//NSArray * books = [fetchedObjects firstObject];
NSLog(#"Books: %#",books);
}
- (void)requestDataFromAPI
{
NSDictionary * apiKeyData = [[NSUserDefaults standardUserDefaults] objectForKey:#"apiKeyData"];
NSString * apiKey = [apiKeyData objectForKey:#"apiKeyData"];
NSLog(#"requestDataFromAPI apiKey: %#",apiKey);
NSString * requestPath = [[NSString alloc] initWithFormat:#"/svc/books/v3/lists/%#?&api-key=%#",_categoryListName, apiKey];
[[RKObjectManager sharedManager]
getObjectsAtPath:requestPath
parameters:nil
success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
[self fetchBooksFromContext];
}
failure: ^(RKObjectRequestOperation *operation, NSError *error)
{
RKLogError(#"Loading from API failed with error: %#", error);
}
];
}
The core data object models are as shown
#import "BookList.h"
NS_ASSUME_NONNULL_BEGIN
#interface BookList (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *listName;
#property (nullable, nonatomic, retain) NSDate *bestsellersDate;
#property (nullable, nonatomic, retain) NSDate *publishedDate;
#property (nullable, nonatomic, retain) NSString *displayName;
#property (nullable, nonatomic, retain) NSNumber *normalListEndsAt;
#property (nullable, nonatomic, retain) NSString *updated;
#property (nullable, nonatomic, retain) NSSet<Book *> *books;
#end
#interface BookList (CoreDataGeneratedAccessors)
- (void)addBooksObject:(Book *)value;
- (void)removeBooksObject:(Book *)value;
- (void)addBooks:(NSSet<Book *> *)values;
- (void)removeBooks:(NSSet<Book *> *)values;
#end
NS_ASSUME_NONNULL_END
#import "Book.h"
NS_ASSUME_NONNULL_BEGIN
#interface Book (CoreDataProperties)
#property (nullable, nonatomic, retain) NSNumber *rankLastWeek;
#property (nullable, nonatomic, retain) NSNumber *weeksOnList;
#property (nullable, nonatomic, retain) NSString *primaryIsbn10;
#property (nullable, nonatomic, retain) NSString *primaryIsbn13;
#property (nullable, nonatomic, retain) NSString *productUrl;
#property (nullable, nonatomic, retain) NSString *bookImageUrl;
#property (nullable, nonatomic, retain) NSString *publisher;
#property (nullable, nonatomic, retain) NSString *bookDescription;
#property (nullable, nonatomic, retain) NSString *title;
#property (nullable, nonatomic, retain) NSString *contributor;
#property (nullable, nonatomic, retain) NSString *author;
#property (nullable, nonatomic, retain) NSNumber *price;
#end
NS_ASSUME_NONNULL_END

Core Data & NSTableView Bindings

I am trying to bind my core data to a NSTableView. I am getting information from an API then wanting to add it to NSTableView. It looks like it is setup correctly because each time I have it call the API and get information back, a blank line is added to the NSTableView data.
Why is it adding a blank line instead of the data I have it binded too?
AppController.h
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
I then am using the new Xcode where it auto synth's.
Items.h
#class TimeLog;
#interface Items : NSManagedObject
#property (nonatomic, retain) NSNumber * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#property (nonatomic, retain) TimeLog *relationship;
#end
Items.m
#implementation Items
#dynamic itemId;
#dynamic title;
#dynamic itemType;
#dynamic relationship;
#end
ItemObject.h
#interface ItemObject : NSObject
#property (nonatomic, retain) NSString * itemId;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * itemType;
#end
ItemObject.m
#implementation ItemObject
#end
Method making API Call
This method makes the API call and adds it to a temp object. It then adds that temp object to core data.
+ (void)searchForItemByType:(NSString *)itemType andId:(NSString *)searchId
{
NSLog(#"Search Feature By ID: %#", searchId);
RKObjectMapping *itemMapping = [RKObjectMapping mappingForClass:[ItemObject class]];
[itemMapping addAttributeMappingsFromDictionary:#{
#"id": #"itemId",
#"name": #"title",
#"item_type": #"itemType"
}];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:itemMapping pathPattern:nil keyPath:#"data" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
// The entire value at the source key path containing the errors maps to the message
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorMessage"]];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError);
// Any response in the 4xx status code range with an "errors" key path uses this mapping
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:#"error_description" statusCodes:statusCodes];
RKObjectManager *manager = [RKObjectManager sharedManager];
NSLog(#"HTTP Client: %#", manager.HTTPClient);
[manager addResponseDescriptorsFromArray:#[ responseDescriptor, errorDescriptor ]];
// NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"false", #"with_lock"
, nil];
NSString *path = [NSString stringWithFormat:#"/api/v1/%#/%#", [itemType lowercaseString], searchId];
NSLog(#"Manager: %#", manager);
[manager getObjectsAtPath:path parameters:params success:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#"Results: %#", [result firstObject]);
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
insertItem = [result firstObject];
NSLog(#"Name: %#", [insertItem title]);
// Handled with articleDescriptor
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Transport error or server error handled by errorDescriptor
NSLog(#"Error: %#", [error localizedDescription]);
NSAlert *alert = [NSAlert alertWithMessageText:#"Error" defaultButton:#"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:#"%#", [error localizedDescription]];
[alert runModal];
}];
}
Log from above code
2013-03-15 10:15:21.817 Project[59074:403] Results: <ItemObject: 0x1034ab360>
2013-03-15 10:15:21.818 Project[59074:403] ManagedObjectContext
2013-03-15 10:15:21.818 Project[59074:403] Name: Custom Mod is missing from Face Lift
IB
I think the problem may be in this code:
NSLog(#"Results: %#", [result firstObject]);
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
insertItem = [result firstObject];
NSLog(#"Name: %#", [insertItem title]);
In your log it looks like [result firstObject] is part of the 'ItemObject' class not the 'Items' class. Even though they share the same structure, 'ItemObject' does not inherit from NSManagedObject, but is being assigned to one. The system doesn't know how to translate an 'ItemObject' object into an 'Items' object so it simply keeps all the values in insertItem blank, which translates into a blank line showing up in your table. Try this instead:
Items *insertItem = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:[[CoreDataHelper sharedInstance] managedObjectContext]];
ItemObject *tempObject = [result firstObject];
insertItem.itemID = tempObject.itemID;
insertItem.title = tempObject.title;
insertItem.itemType = tempObject.itemType;

NSMutableArray can not add object?

I have 2 model:
#interface Program : NSObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, strong) NSMutableArray *guides;
#end
#interface Guide : NSObject
#property (nonatomic, retain) NSString *name;
#end
And I add some guides to program from one xml:
Program *program = [Program new];
program.name = #"My list"
for(DDXMLElement *guideElement in [programElement nodesForXPath:#"guide" error:&error])
{
Guide *guide = [Guide new];
guide.name = [guideElement stringValue];// [p attribute:#"name"];
[program.guides addObject:guide];
NSLog(#"load guide number: %d", [program.guides count]);
}
The out is always "load guide number: 0"
program.guides is nil, since you never created it.
In your Program's init method, add:
self.guides = [[NSMutableArray alloc] init];
Or, more sloppily, before your for loop add:
program.guides = [[NSMutableArray alloc] init];

Inherited Properties not working

I am having issues getting something to work in objective c. I have the following protocol:
#protocol PFItem <NSObject>
- (NSNumber *)weight;
- (NSNumber *)cost;
#end
This protocol is implemented in the following class:
#interface PFItemObject : NSObject <NSCoding, PFItem> {
NSString *name;
NSNumber *weight;
NSNumber *cost;
}
#property (retain, nonatomic) NSString *name;
#property (retain, nonatomic) NSNumber *weight;
#property (retain, nonatomic) NSNumber *cost;
#property (readonly) NSString *className;
+ (id)itemWithString:(NSString *)string;
#end
Now, this works well for me, except when I use the PFItemObject as a superclass like so:
#interface PFWeaponObject : PFItemObject <NSCoding, PFItem> {
NSString *damage;
NSString *critical;
NSString *range;
NSNumber *attackBonus;
NSNumber *damageBonus;
WeaponTypes type;
BOOL isTwoHanded;
}
#property (retain, nonatomic) NSString *damage;
#property (retain, nonatomic) NSString *critical;
#property (retain, nonatomic) NSString *range;
#property (retain, nonatomic) NSNumber *attackBonus;
#property (retain, nonatomic) NSNumber *damageBonus;
#property WeaponTypes type;
#property BOOL isTwoHanded;
+ (PFWeaponObject *)unarmedWeapon;
#end
The +itemWithString: method, in the PFWeaponObject works like this:
+ (id)itemWithString:(NSString *)string {
NSArray *components = [string componentsSeparatedByString:#";"];
PFWeaponObject *weapon = [[PFWeaponObject alloc] init];
[weapon setName:[components objectAtIndex:0]];
[weapon setWeight:[NSNumber numberWithFloat:[[components objectAtIndex:1] floatValue]]];
[weapon setCost:[NSNumber numberWithInt:[[components objectAtIndex:2] intValue]]];
[weapon setDamage:[components objectAtIndex:3]];
[weapon setCritical:[components objectAtIndex:4]];
[weapon setRange:[components objectAtIndex:5]];
[weapon setType:[[components objectAtIndex:6] integerValue]];
[weapon setAttackBonus:[NSNumber numberWithInt:[[components objectAtIndex:7] intValue]]];
[weapon setDamageBonus:[NSNumber numberWithInt:[[components objectAtIndex:8] intValue]]];
[weapon setIsTwoHanded:[[components objectAtIndex:9] boolValue]];
return [weapon autorelease];
}
I assumed that, because I have inherited from the PFItemObject, I should be able to assign values to the superclass' properties without problem. But when I do the following:
- (void)testItemCreationStrings {
NSString *weaponString = #"+1 Greatsword;25;2500;2d6;x3;Melee;5;1;1;YES";
PFWeaponObject *sampleWeapon = [PFWeaponObject itemWithString:weaponString];
}
All properties from the superclass (PFItemObject) all return #"+1 Greatsword". Did I miss something somewhere that I should have done?
Thanks for any help you can provide, and please, feel free to ask for more information if you need it.
Edit because the question was edited. I think I have a solution.
Try this:
+ (id)itemWithString:(NSString *)string {
NSArray *components = [string componentsSeparatedByString:#";"];
PFWeaponObject *weapon = [[PFWeaponObject alloc] init];
[weapon setName:[components objectAtIndex:0]];
[weapon setWeight:[NSNumber numberWithInt:[[components objectAtIndex:1] intValue]]];
[weapon setCost:[NSNumber numberWithInt:[[components objectAtIndex:2] intValue]]];
// etc.
// etc.
}
I think you are not setting the behavior and format of the NSNumberFormatter, and I also think that using it is unnecessary. There are other examples of how to use number formatters if you really want to do it that way for some reason. If there is a reason you want/need to, I can provide direction.

Problem creating NSManagedObject derived class

I am doing something wrong here... I know that
I'm using Xcode and I have created the following class using the data modeller:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Project : NSManagedObject {
#private
}
#property (nonatomic, retain) NSNumber * indent;
#property (nonatomic, retain) NSNumber * collapsed;
#property (nonatomic, retain) NSString * color;
#property (nonatomic, retain) NSNumber * project_id;
#property (nonatomic, retain) NSNumber * item_order;
#property (nonatomic, retain) NSNumber * cache_count;
#property (nonatomic, retain) NSNumber * user_id;
#property (nonatomic, retain) NSString * name;
#end
When I am trying to propagate this class with data from a JSON source using the following code:
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"projects" ofType:#"json"];
if (filePath) {
NSString* jsonString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
DLog(#"JSON for Projects:%#", jsonString);
SBJsonParser* jsonParser = [SBJsonParser new];
id response = [jsonParser objectWithString:jsonString];
NSArray* array = (NSArray*) response;
NSEnumerator* e = [array objectEnumerator];
NSDictionary* dictionary;
while ((dictionary = (NSDictionary*)[e nextObject])) {
Project* project = [[Project alloc] init];
project.user_id = [dictionary objectForKey:#"user_id"];
project.name = [dictionary objectForKey:#"name"];
project.color = [dictionary objectForKey:#"color"];
project.collapsed = [dictionary objectForKey:#"collapsed"];
project.item_order = [dictionary objectForKey:#"item_order"];
project.cache_count = [dictionary objectForKey:#"cache_count"];
project.indent = [dictionary objectForKey:#"indent"];
project.project_id = [dictionary objectForKey:#"project_id"];
[elementArray addObject:project];
[project release];
}
}
However, the code stops at the project.user_id = [dictionary objectForKey:#"user_id"]; line with an exception "* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Project setUser_id:]: unrecognized selector sent to instance 0x590bcb0'"
I don't know why this is happening or how to resolve this.
I've set up a reality distortion field so I don't violate my NDA. And now I can answer your question, it has nothing to do with the product-that-must-not-be-named anyway.
There is your bug: Project* project = [[Project alloc] init];
The #dynamic setters and getters are not created for you if you create your object this way.
You can't use NSManagedObjects without a NSManagedObjectContext.
You should use something like this:
Project *project = (Project *)[NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
Property names with underscores are not very sensible in the Objective C world - I guess the properties generated by Core Data have the wrong names therefore. Try using CamelCase, that is calling your properties userID, itemOrder, cacheCount etc.
You may need to set up your getters and setters.
It could be as simple as adding:
#synthesize user_id;
In your class file.