RestKit loading SQLite Data - objective-c

My idea is to load an existing sqlite database filled with data into my object store for RestKit. If I do the following, it works and I got the data.
RKURL *baseURL = [RKURL URLWithBaseURLString:#"http://www.myurl.com"];
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:DB_FILENAME_SEED_DB usingSeedDatabaseName:DB_FILENAME_SEED_DB managedObjectModel:nil delegate:self];
objectManager.objectStore = objectStore;
But what I want to do is to make the loading into the store later on in a method, something like that:
- (void)loadSeedDatabase
{
[FileUtility deleteNormalDatabaseFile]; // do I have to do that?
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:DB_FILENAME usingSeedDatabaseName:DB_FILENAME managedObjectModel:
[[objectManager objectStore] managedObjectModel] delegate:self];
objectManager.objectStore = objectStore;
}
I am not sure if I have to delete the already created local sqlite db. If I call this method, locally the DB is correct - my filled sqlite db is there. But if I do some fetch requests, nothing is coming back.
Andy idea what I have to change or what the problem is?

Related

Ignoring default value for missed elements instead of setting nil with RestKit

Im trying to send a response without certain fields, and RestKit still keeps setting the values to nil even after i have disabled this option using:
infoMapping.assignsDefaultValueForMissingAttributes = NO;
Full mapping code:
RKEntityMapping *infoMapping = [RKEntityMapping mappingForEntityForName:#"IndexInfo" inManagedObjectStore:managedObjectStore];
//userMapping.identificationAttributes = #[#"id"];
[infoMapping addAttributeMappingsFromDictionary:#{
#"first" : #"first",
#"last" : #"last"
}];
infoMapping.assignsDefaultValueForMissingAttributes = NO;
RKResponseDescriptor *infoResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:infoMapping
method:RKRequestMethodGET
pathPattern:#"/core/feed.json"
keyPath:#"info"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
I really need to be able to respond with partial data, if this is a bug is there another way around it to hook the mapping to disable it manually?
It turns out that it was creating new entity's each time for my infoMapping.
So a workaround iv included an id attribute to the response, mapped this addition and used it as the identificationAttribute. This probably isn't the best solution but it works for now. (So my id is always 0 in my response)
I also included some validations within my IndexInfo model.
infoMapping.identificationAttributes = #[#"id"];
[infoMapping addAttributeMappingsFromDictionary:#{
#"id" : #"id",
#"first" : #"first",
#"last" : #"last"
}];
validation:
- (BOOL)validateFirst:(id *)ioValue error:(NSError **)outError { ....
I also needed:
infoMapping.performsKeyValueValidation = YES;
infoMapping.discardsInvalidObjectsOnInsert = YES;
Instead of
// infoMapping.assignsDefaultValueForMissingAttributes = NO;
The id is not ever used so I'l figure out a more elegant way and update answer.

Restkit Object Mapping Two Classes Bad Access

I am mapping two classes of objects with RestKit. When I do either of them by themselves, it works out perfectly fine. But when I call them together, it will crash on line 449 in RKObjectMappingOperation.m,
[destinationSet setSet:destinationObject];
With an "EXC_BAD_ACCESS" error.
Here are my two mapping methods:
- (RKObjectLoader *)saves
{
// Create an object manager and connect core data's persistent store to it
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"Db.sqlite"];
objectManager.objectStore = objectStore;
// Define our author mapping for saved places
RKManagedObjectMapping *authorMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Person"];
[authorMapping mapAttributes:#"uid", nil];
// Define our place mapping
RKManagedObjectMapping *placeMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Place"];
[placeMapping mapAttributes:#"uid",#"name",#"address", nil];
// Now, connect the two via a save
RKManagedObjectMapping *saveMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Save"];
[saveMapping mapAttributes:#"uid", #"timestamp", nil];
[saveMapping mapKeyPath:#"place" toRelationship:#"place" withMapping:placeMapping];
[saveMapping mapKeyPath:#"author" toRelationship:#"author" withMapping:authorMapping];
// We expect to find the place entity inside of a dictionary keyed "saves"
[objectManager.mappingProvider setMapping:saveMapping forKeyPath:#"saves"];
// Prepare our object loader to load and map objects from remote server, and send
RKObjectLoader *objectLoader = [objectManager objectLoaderWithResourcePath:#"places/saves" delegate:self];
objectLoader.method = RKRequestMethodGET;
[objectLoader send];
return objectLoader;
}
- (RKObjectLoader *)recommends
{
// Create an object manager and connect core data's persistent store to it
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"Db.sqlite"];
objectManager.objectStore = objectStore;
// Define our author mapping for recommended places
RKManagedObjectMapping *authorMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Person"];
[authorMapping mapAttributes:#"uid", nil];
// Define our place mapping
RKManagedObjectMapping *placeMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Place"];
[placeMapping mapAttributes:#"uid",#"name",#"address", nil];
// Now, connect the two via a recommend
RKManagedObjectMapping *recommendMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Recommend"];
[recommendMapping mapAttributes:#"uid", #"timestamp", nil];
[recommendMapping mapKeyPath:#"place" toRelationship:#"place" withMapping:placeMapping];
[recommendMapping mapKeyPath:#"author" toRelationship:#"author" withMapping:authorMapping];
// We expect to find the place entity inside of a dictionary keyed "recommends"
[objectManager.mappingProvider setMapping:recommendMapping forKeyPath:#"recommends"];
// Prepare our object loader to load and map objects from remote server, and send
RKObjectLoader *objectLoader = [objectManager objectLoaderWithResourcePath:#"places/recommends" delegate:self];
objectLoader.method = RKRequestMethodGET;
[objectLoader send];
return objectLoader;
}
When I call one, or the other, it works. When I call both,
[self saves];
[self recommends];
It crashes. Any idea why?
The call to set objectManager.objectStore is causing the previously set objectStore to be released from objectManager. The first call, [self saves] creates and sets an objectStore object, then second call [self recommends] repeats this, thereby removing the first one set in [self saves]. Some time during the processing started off by [self saves], the original (released) objectStore object is being accessed, hence the crash.
A potential fix would be to refactor out the objectStore setter into a separate method which is called by both methods, or wrap it in an if (!objectManager.objectStore) { ... } statement.

How to map RKClient's RKResponse with an RKObjectMapping

I am doing a multi-part posting of image data and some values using RestKit's RKClient like so:
RKParams* params = [RKParams params];
[params setValue:foo.accountId forParam:#"accountId"];
[params setValue:foo.identifier forParam:#"fooId"];
[params setValue:_photoId forParam:#"photoId"];
[params setData:data MIMEType:#"image/png" forParam:#"image"];
[[RKClient sharedClient] post:#"/foo/uploadPhoto" params:params delegate:self];
This works great, and my backend server responds with JSON representation of the server side model object, it look like this:
{"id":"4ee2b4670364720c089e75b9","accountId":"4ebee3469ae2d8adf983c561","fooId":"4ec0983d036463d900841f0b","photoId":"E5B20AF1-9F10-4175-8262-852BDA3DEDE9","filename":"4ebee3469ae2d8adf983c561_4ec0983d036463d900841f0b_E5B20AF1-9F10-4175-8262-852BDA3DEDE9","contentType":"image/png"}
What I need to do now is map this to my client side (iOS) model object. The client side model object is almost the same, but not identical (so using RKJSONParser's objectFromString method is not an option), therefore I have a custom RKObjectMapping defined that handles the mapping. RKClient's delegate only gets a RKResponse, so how can I use the response along with the mapper to get an instance of my client side model object?
Note: To be clear, I am very familiar how this works when using RKObjectManager to post an object and map a response. The unique part of my situation is that I am using RKClient to achieve the multi-part post. Unfortunately RKClient doesn't seem to have simple methods available to handle response mapping like RKObjectManager does... unless I am missing something (which I hope and am and you all will point out for me ;).
Well, this post was similar (but non-functional) and it gave me some ideas of a new technique of using this method on RKObjectLoader
- (RKObjectLoader *)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void ( ^ ) ( RKObjectLoader *))block
So now I could get the benefit of mapping that wasn't obvious how to get using RKClient.
Router setup:
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:kApiUrlBase];
[objectManager.router routeClass:[PAPetPhoto class] toResourcePath:#"/pet/uploadPhoto" forMethod:RKRequestMethodPOST];
Mapping setup:
RKObjectMapping *papetPhotoMapping = [RKObjectMapping mappingForClass:[PAPetPhoto class]];
[papetPhotoMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[papetPhotoMapping mapAttributes:#"accountId", #"petId", #"photoId", #"filename", #"contentType", nil];
[objectManager.mappingProvider addObjectMapping:papetPhotoMapping];
[objectManager.mappingProvider setSerializationMapping:[papetPhotoMapping inverseMapping] forClass:[PAPetPhoto class]];
[objectManager.mappingProvider setMapping:papetPhotoMapping forKeyPath:#"petPhoto"];
The post: (notice since I built up all my params in the block my object is just a dummy instance to trigger the proper routing and mapper).
PAPetPhoto *photo = [[PAPetPhoto alloc] init];
[[RKObjectManager sharedManager] postObject:photo delegate:self block:^(RKObjectLoader *loader){
RKParams* params = [RKParams params];
[params setValue:pet.accountId forParam:#"accountId"];
[params setValue:pet.identifier forParam:#"petId"];
[params setValue:_photoId forParam:#"photoId"];
[params setValue:_isThumb ? #"THUMB" : #"FULL" forParam:#"photoSize"];
[params setData:data MIMEType:#"image/png" forParam:#"image"];
loader.params = params;
}];
Server endpoint (Java, Spring MVC)
#RequestMapping(value = "/uploadPhoto", method = RequestMethod.POST)
#ResponseBody
public Map<String, Object> handleFormUpload(#RequestParam("accountId") String accountId,
#RequestParam("petId") String petId,
#RequestParam("photoId") String photoId,
#RequestParam("photoSize") PhotoSizeEnum photoSize,
#RequestParam("image") Part image) throws IOException {
if (log.isTraceEnabled())
log.trace("uploadPhoto. accountId=" + accountId + " petId=" + petId + " photoId=" + photoId + " photoSize=" + photoSize);
PetPhoto petPhoto = petDao.savePetPhoto(accountId, petId, photoId, photoSize, image);
Map<String, Object> map = GsonUtils.wrapWithKeypath(petPhoto, "petPhoto");
return map;
}
Server response JSON (note the keyPath of "petPhoto" that corresponds to the mapping setup):
{
petPhoto = {
accountId = 4ebee3469ae2d8adf983c561;
contentType = "image/png";
filename = "4ebee3469ae2d8adf983c561_4ec0983d036463d900841f09_3FED4959-1042-4D8B-91A8-76AA873851A3";
id = 4ee2e80203646ecd096d5201;
petId = 4ec0983d036463d900841f09;
photoId = "3FED4959-1042-4D8B-91A8-76AA873851A3";
};
}
Delegate:
- (void) objectLoader:(RKObjectLoader*)objectLoader didLoadObject:(id)object {
if ([objectLoader wasSentToResourcePath:#"/pet/uploadPhoto"]) {
PAPetPhoto *photo = (PAPetPhoto*)object;
}
}

Mapping a JSON response to an object using RestKit and Objective-C

I am relatively new to Objective-C and am attempting to use RestKit to receive a JSON response from a web service. I have successfully received the data back to my application, which looks like this viewing the response:
{id:"1","Translation":"Test"}
I would like to map this translation to my "Translation" object in my application, but have tried a few different ways but am not sure how to achieve this.
So my questions are:
How can I map this response to my Translation object
Am I doing this correctly, creating a method to complete this call outwit my view controller?
My Translation Object
#implementation Translation
#synthesize identifier = _identifier;
#synthesize translation = _translation;
- (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"id", #"identifier",
#"translation", #"translation",
nil];
}
#end
My Translate Method
- (NSString *)performTranslation:(NSString *)translation
{
NSString *data = [[NSString alloc] initWithFormat:#"{\"SourceId\": \"%#\",\"RegionTag\": \"%#\",\"InputString\": \"%#\"}", #"1", #"Glasgow", translation];
NSString *post = data;
RKRequest *MyRequest = [[RKRequest alloc] initWithURL:[[NSURL alloc] initWithString:#"http://my.url.com/Translation/Translate"]];
MyRequest.method = RKRequestMethodPOST;
MyRequest.HTTPBodyString = post;
MyRequest.additionalHTTPHeaders = [[NSDictionary alloc] initWithObjectsAndKeys:#"application/json", #"Content-Type", #"application/json", #"Accept", nil];
[MyRequest send];
RKResponse *Response = [MyRequest sendSynchronously];
return Response.bodyAsString; <--- looking to map this to translation object here
}
The snippet of your code seems a bit outdated. I strongly recommend reading the newest Object Mapping guide in order to leverage RestKit into it's fullest potential - especially the part Mapping without KVC.
Edit:
In order to post an object with RestKit and receive back an answer, we define a TranslationRequest class that will hold our request & Translation to hold our response.
Firstly, we set up our RKObjectManager and mappings (i usually do this in my AppDelegate):
RKObjectManager *manager = [RKObjectManager objectManagerWithBaseURL:kOurBaseUrl];
[manager setSerializationMIMEType:RKMIMETypeJSON];
//this is a singleton, but we keep the manager variable to avoid using [RKObjectManager sharedManager] all the time
//Here we define a mapping for the request. Note: We define it as a mapping from JSON to entity and use inverseMapping selector later.
RKObjectMapping *translationRequestMapping = [RKObjectMapping mappingForClass:[TranslationRequest class]];
[translationRequestMapping mapKeyPath:#"RegionTag" toAttribute:#"regionTag"];
...
[[manager mappingProvider] setSerializationMapping:[translationRequestMapping inverseMapping] forClass:[TranslationRequest class]];
//now we define the mapping for our response object
RKObjectMapping *translationMapping = [RKObjectMapping mappingForClass:[Translation class]];
[translationMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[translationMapping mapKeyPath:#"Translation" toAttribute:#"translation"];
[[manager mappingProvider] addObjectMapping:mapping];
//finally, we route our TranslationRequest class to a given endpoint
[[manager router] routeClass:[TranslationRequest class] toResourcePath:kMyPostEndpoint];
This should be enough of the necessary setup. We can call our backend anywhere in the code (e.g. in any controller) like this:
//we create new TranslationRequest
TranslationRequest *request = [[TranslationRequest alloc] init];
[request setRegionTag:#"Hello"];
....
//then we fetch the desired mapping to map our response with
RKObjectMapping *responseMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:class]
//and just call it. Be sure to let 'self' implement the required RKObjectManagerDelegate protocol
[[RKObjectManager sharedManager] postObject:request mapResponseWith:responseMapping delegate:self];]
Try this approach and let me know if you need any assistance.. I was not able to test it fully as i don't have any suitable backend that will return the responses, but judging from the RestKit log this should work.
You need to pass the returned JSON string into a JSON parser. I use SBJSON. You can then use the resulting dictionary to populate the properties of your object.
RestKit seems to have native objects that encapsulate four different JSON parsers. However, I'd advise caution because they seem to assume that the top level parsed object will always be a dictionary.
As another aside, the example in your question is not valid JSON. It should look like this:
{"id":"1","Translation":"Test"}

loadObjectsAtResourcePath (RestKit)

I have a method like this.
- (void)loadData {
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:8080/activiti-rest/service"];
[manager loadObjectsAtResourcePath:#"/process-definitions?start=0&size=10&sort=id&order=asc" objectClass:[Data class] delegate:self];
}
I get an error that says:
instance method '-loadObjectsAtResourcePath:objectClass:delegate' not found('return types default to 'id' ').
Can someone please help me how to call loadObjectsAtResourcePath function?
Maybe you want to define the mapping manually. Then just do something like this:
RKObjectMapping *mapping = [[[RKObjectManager sharedManager] mappingProvider] objectMappingForClass:[Data class]];
RKObjectLoader *loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/process-definitions?start=0&size=10&sort=id&order=asc" objectMapping:mapping delegate:self];
Don't forget to set base resource path to your RKObjectManager singleton.
I had to use the prototype that has objectMapping in it because i was trying to map the response to my own mapping called Data.
So i created an RKObjectMapping object ,configured to my own mapping class and then used this function.
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[Data class]];
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:#"http://localhost:8080/activiti-rest/service"];
[manager loadObjectsAtResourcePath:#"/process-definitions?start=0&size=10&sort=id&order=asc" objectMapping:mapping delegate:self] ;
It worked!
Without knowing RestKit This sounds like a simple header problem. look at the header where "RKObjectManager" is defined. (Cmd click it in XCode to make sure it's available in your project.) Double check the method definition and make sure you're calling it correctly. Always use auto-complete to invoke methods.
Is your object/class implementing the in the interface?