I'm using the code below to post a managedObject. From the server, I'm not getting anything back, except 200, so there is no mapping for response object.
[self.objectManager postObject:invitation path:#"/path" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
Is there a dummy format I can use for RKResponseDescriptor ?
RKResponseDescriptor *responseDescriptor = nil; //causes a crash
There is no 'dummy' RKResponseDescriptor instance, you must explicitly define what you want RestKit to do with the response data.
Your response descriptor can be partial, so it just maps one field to prove things are working.
Or, you could use a dynamic mapping to read the keys in the incoming data and create a mapping to NSMutableDictionary which includes all of the keys.
Both of these things are only really useful for testing, but may assist you.
If the response actually has no body data and you just want to monitor for success then you can either use AFNetworking instead, or create the response descriptor with a mapping to NSMutableDictionary and with no keys. RestKit should pick that up and it should act as a 'dummy' for your purposes. It will create a dictionary instance that is never used, but you can just ignore that.
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping
method:RKRequestMethodPOST
pathPattern:#"/path"
keyPath:nil
statusCodes:[self statusCodeSuccess]];
Related
I'm downloading often identical data on multiple calls to different URLs in RESTKit. The first call maps fine, but the subsequent calls then replace the first call's entities.
The behaviour that I want is to have the objects unique to within their parent entity, so even if the data looks the same I still want a new object to be created, but at the moment it looks like RESTKit wants them to be unique across the entire database. There's no unique key to do this in the data I'm downloading, the objects are literally identical. Below is the code I'm using to create the operation. How do I set this up to allow duplicates?
NSMutableURLRequest *request = [self requestWithURL:URL];
[request setHTTPMethod:#"GET"];
RKHTTPRequestOperation *requestOperation = [[RKHTTPRequestOperation alloc]initWithRequest:request];
RKResponseDescriptor *responseDescriptor = [INKResponseDescriptorFactory journeySegmentDescriptorForPath:URL.path inStore:[RKObjectManager sharedManager].managedObjectStore];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithHTTPRequestOperation:requestOperation
responseDescriptors:#[responseDescriptor]];
operation.managedObjectContext = [self.objectManager context];
operation.managedObjectCache = self.objectManager.managedObjectStore.managedObjectCache;
operation.savesToPersistentStore = NO;
[operation setCompletionBlockWithSuccess: ^(RKObjectRequestOperation *requestOperation, RKMappingResult *mappingResult)
{
success();
} failure: ^(RKObjectRequestOperation *requestOperation, NSError *error) {
failure(error);
}];
[self.objectManager enqueueObjectRequestOperation:operation];
Figured out a way to do this.
Each time I'm updating the data, even though the entities I get contain identical data, I'm retrieving them from a different url.
RESTKit allows you to retrieve metadata from you calls and map this into your managed objects properties.
So what I've done is map the URL I used for the request into a property and used that as well as an identifier, which is unique within this call's returned objects, to make objects which will be unique throughout the database.
So my mapping now looks like this:
RKEntityMapping *seatMapping = [RKEntityMapping mappingForEntityForName:#"Seat" inManagedObjectStore:store];
[seatMapping addAttributeMappingsFromDictionary:#{ #"designator" : #"designator",
#"status" : #"status",
#"#metadata.HTTP.request.URL" : #"requestURL"}];
seatMapping.identificationAttributes = #[#"requestURL", #"designator"];
I'm using AFNetworking and MagicalRecord (the current develop branch) and I'm trying to figure out how to import a lot of objects which are dependent on each other. Each resource/entity has multiple pages worth of downloads. I have a class managing the downloads for a given entity and saving them using MagicalDataImport (which has been amazing).
I believe my issue is that the imports aren't happening on the same thread. So I think what is happening is:
In one thread, EntityA is getting saved properly and propagated to the parent entity.
Then in another thread, EntityB is being saved, and along with it it's relationship to EntityA is built. That means a blank (fault?) object is being created. Then when it gets propagated to the parent entity, I believe EntityA is overwriting the EntityA that is there. Thus I'm left with some objects that don't have all of the attributes.
At least, I think that is what is happening. What I'm seeing via the UI is actually that the relationships between entities aren't always built correctly.
My end goal is to get the entire download/import process to be done in the background, not effecting the UI at all.
Here is my AFJSONRequest:
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self saveResources:[JSON objectForKey:#"data"]];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
DLog(#"%#",error.userInfo);
[self.webService command:self didFail:error.localizedDescription];
}];
[operation setQueuePriority:self.priority];
And it calls saveResources::
- (void)saveResources:(NSArray*)resources {
BOOL stopDownloads = [self stopDownloadsBasedOnDate:resources];
if ([resources count] > 0 && !stopDownloads){
self.offset = #([offset intValue] + [resources count]);
[self send];
}
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *blockLocalContext) {
[self.classRef MR_importFromArray:resources inContext:blockLocalContext];
} completion:^(BOOL success, NSError *error) {
if (error){
// ... handle errors
}
else {
// ... handle callbacks
}
}];
}
This kicks off another download ([self send]) and then saves the objects.
I know by default AFNetworking calls the callback in the main queue, and I've tried setting the SuccessCallbackQueue/FailureCallbackQueue to my background thread, but that doesn't seem to solve all the issues, I still have some relationships going to faulted objects, though I think I do need to do that to keep everything going in a background thread.
Is there anything else I need to call in order to properly propagate these changes to the main context? Or is there a different way I need to set this up in order to make sure that all the objects are saved correctly and the relationships are properly built?
Update
I've rewritten the issue to try to give more clarification to the issues.
Update
If you need more code I created a gist with (I believe) everything.
I ended up having this exact same issue a few days ago. My issue was I had received a customer record from my API with AFNetworking. That customer could have pets, but at this point I didn't have the petTypes to correspond to the customers pet record.
What I did to resolve this was create a transformable attribute with an NSArray which would temporarly store my pets until my petTypes were imported. Upon the importation of petTypes I then triggered an NSNotificationCenter postNotification (or you can just do the pet import in the completion).
I enumerated through the temporary transformable attribute that stored my pet records and then associated the with the petType
Also I see you are doing your import inside of a save handler. This is not needed. Doing your MR_importFromArray will save automatically. If you are not using an MR_import method then you would use the saveToPersistentStore methods.
One thing is I don't see where you are associating the relationships. Is EntityB's relationship to EntityA being sent over via JSON with the EntityA objecting being in EntityB?
If so then this is where the relationship is getting messed up as it is creating / overwriting the existing EntityA for the one provided in EntityB. My recommendation would be to do something like this.
NSArray *petFactors = [responseObject valueForKeyPath:#"details.items"];
NSManagedObjectContext *currentContext = [NSManagedObjectContext MR_context];
Pets *pet = [Pets MR_findFirstByAttribute:#"id" withValue:petId inContext:currentContext];
pet.petFactors = nil;
for (id factor in petFactors) {
[pet addPetFactorsObject:[PetFactors MR_findFirstByAttribute:#"id" withValue:[factor valueForKey:#"factorId"]]];
}
[currentContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"SAVED PET FACTORS");
[[NSNotificationCenter defaultCenter] postNotificationName:kPetFactorsSavedSuccessfully object:nil];
} else {
NSLog(#"Error: %#", error);
}
}];
I'm putting this as an answer, though I'm not 100% sure if this is your issue or not. I think the issue stems from your localContext. Here is a sample web request method from an app we wrote that uses data importing, you may be able to use it as an example to get yours working.
Note that the AFNetworking performs its completion block on the main thread, then the MagicalRecord saveInBackground method switches back to a background thread to do the importing and processing, then the final MR completion block performs the handler block on the main thread again. The localContext that's used to import is created/managed by the saveInBackground method. Once that method is complete the context is saved and merged with the app's main context and all the data can then be accessed.
- (void)listWithCompletionHandler:(void (^)(BOOL success))handler{
[[MyAPIClient sharedClient] getPath:#"list.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject){
NSString *statusString = [responseObject objectForKey:#"status"];
// Handle an error response
if(![statusString isKindOfClass:[NSString class]] || ![statusString isEqualToString:#"success"]){
// Request failure
NSLog(#"List Request Error: %#", statusString);
NSLog(#"%#", [responseObject objectForKey:#"message"]);
if(handler)
handler(NO);
return;
}
NSArray *itemsArray = [responseObject objectForKey:#"items"];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
// Load into internal database
NSArray *fetchedItems = [Item importFromArray:itemsArray inContext:localContext];
NSLog(#"Loaded %d Items", [fetchedItems count]);
} completion:^{
if(handler)
handler(YES);
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"Fail: %#", error);
if(handler)
handler(NO);
}];
}
I have an NSPersistentDocument subclass using NSManagedObject subclasses for my data.
When a new document is opened, I do some initializing of data structures (trivial amount of populating fields). What I've noticed is that the Untitled document gets autosaved, and when the application re-opens, that document gets loaded. If the application quits, the user doesn't (by default) get prompted with the save dialog. If the window closes, the user does.
First question:
I want to call up the save dialog when the user quits the application. I don't want this Untitled document hanging around (under normal circumstances). I either want it saved or trashed.
I attempted to fill out:
- (void)applicationWillTerminate:(NSNotification *)aNotification
In order to trigger the document to be saved. Calling save: on the context at this point gives an error. From what I can tell, this is because the user hasn't yet saved the file on their own. In addition, calling [self close]; or [[self windowForSheet] close]; close the window without saving.
How can I force the save dialog to come up? How can I trash the untitled document?
Second question (no, I can't count):
Since when the application starts, there may or may not be an Untitled document to deal with, I'm trying to keep track of the state in another model. I've already found that the initial data (to which I referred earlier) is present when the Untitled document came up. My other model has some metadata, including a success flag/state for the populated data. Once the populated data is all in place and correct, the state indicates as such. Unfortunately, while my populated data is being loaded when the app starts with a pre-existing Untitled document, the metadata class is not.
Please excuse the roughness of the code, at this point, I'm mucking it up until I can see that it's working how I want before I polish it back off:
- (bool) createGameState {
NSEntityDescription* description = [NSEntityDescription entityForName:[GameState name] inManagedObjectContext:[self managedObjectContext]];
NSFetchRequest* req = [[NSFetchRequest alloc] init];
[req setEntity:description];
NSError *error = nil;
NSArray *array = [[self managedObjectContext] executeFetchRequest:req error:&error];
[req release];
req = nil;
GameState* result = nil;
if (array) {
NSUInteger count = [array count];
if (!count) {
// Create the new GameState.
DebugLog(#"Creating GameState");
result = [NSEntityDescription insertNewObjectForEntityForName:[GameState name] inManagedObjectContext:[self managedObjectContext]];
[result setIsLoaded:[NSNumber numberWithBool:NO]];
} else {
if (count > 1) {
NSLog(#"WARNING: Potentially Corrupt Game State. found: %lu", count);
}
result = [array objectAtIndex:0];
if ([result isLoaded]) {
[self variantLoaded];
} else {
// In this case, we have an aborted set-up. Since the game isn't
// playable, just refuse to create the GameState. This will
// force the user to create a new game.
return NO;
}
}
} else {
DebugLog(#"error: %#", error);
}
[game setState:result];
return result;
}
Note that array is always present, and count is always zero. No, I'm not explicitly calling save: anywhere. I'm relying on the standard auto-save, or the user performing a save.
EDIT:
I installed the Core Data Editor app. It turns out the issue isn't on saving the data, but on loading it. (Note: Due to another issue, the app saves as binary when instructed to save as XML, which causes much head banging.)
I've broken it down to the simplest code, which should pick up all objects of type GameState in an array. It retrieves none, despite there clearly being objects of the appropriate type in the saved file:
NSManagedObjectContext* moc = [self managedObjectContext];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"GameState" inManagedObjectContext:moc];
NSFetchRequest* req = [[NSFetchRequest alloc] init];
[req setEntity:entity];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:req error:&error];
Array is not null, but [array count] is 0.
At this point, I'm guessing it's something simple that I'm overlooking.
Second EDIT:
I added -com.apple.CoreData.SQLDebug 5 and saved as SQLite. The call to executeFetchRequest does not generate any debug logs. I do see the INSERT INTO ZGAMESTATE entry show up in the logs. It seems that executeFetchRequest is not getting passed to the backend.
Third EDIT (this one burns):
I created a new xcode project, using core data (as I had with the other). I copied just this one function (stubbing where necessary) and plopped a call to it in windowControllerDidLoadNib. In this new project, the code above works.
Found the problem.
I errantly was loading objects in Document's - (id) init call. Moved to windowControllerDidLoadNib (which is what I did in the test version) and it worked fine.
Is there a way to inspect a NSPredicate object for the purposes of serializing it into a URL? I am trying to retrieve the data remotely and need to translate the predicate object into a URL with querystring parameters that the server understands.
This was inspired by a talk given in WWDC 2010 called "Building a Server Driven User EXperience" where the speakers talk about using Core-Data and with a server backend. I have followed the session video and slides, but am stuck on the serializing point. For example, there is a Person object, and I'm trying to fetch all people whose first name is "John". I am using a subclass of NSManagedObjectContext called RemoteManagedObjectContext, which overrides the executeFetchRequest method, and is supposed to send the call to the server instead. The fetch request is being created as (ellipsed non-essential parts):
#implementation PeopleViewController
- (NSArray *)getPeople {
RemoteFetchRequest *fetchRequest = [[RemoteFetchRequest alloc] init];
NSEntityDescription *entity = ...
NSPredicate *template = [NSPredicate predicateWithFormat:
#"name == $NAME AND endpoint = $ENDPOINT"];
NSPredicate *predicate = [template predicateWithSubstitutionVariables:...];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
// the custom subclass of NSManagedObjectContext executes this
return [remoteMOC executeFetchRequest:fetchRequest error:&error];
}
#end
Now inside the custom subclass of NSManagedObjectContext, how can I serialize the fetch request into querystring parameters suitable for the server. So given the above fetch request, the corresponding URL would be:
http://example.com/people?name=John
It is possible to get a string representation of the predicate which returns,
name == "John" AND endpoint == "people"
that I can parse to get the parameters name, and endpoint. However, is it possible to do it without parsing the string? Here's a partial implementation of the RemoteManagedObjectContext class.
#implementation RemoteManagedObjectContext
- (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error {
// this gives name == "John" AND endpoint == "people"
// don't know how else to retrieve the predicate data
NSLog(#"%#", [[request predicate] predicateFormat]);
...
}
#end
Even better than a string representation is an object-oriented representation! And it's done automatically!
First, check the class of the NSPredicate. It will be an NSCompoundPredicate. Cast it to an appropriate variable.
You'll then see that it's compoundPredicateType is NSAndPredicateType, just like you'd expect.
You can also see that the array returned by -subpredicates reveals 2 NSComparisonPredicates.
The first subpredicate has a left expression of type NSKeyPathExpressionType and a -keyPath of #"name", the operator is NSEqualToPredicateOperatorType. The right expression will be an NSExpression of type NSConstantValueExpressionType, and the -constantValue will be #"John".
The second subpredicate will be similar, except that the left expression's keyPath will be #"endpoint", and the right expression's constantValue will be #"people".
If you want more in-depth information on turning NSPredicates into an HTTP Get request, check out my StackOverflow framework, "StackKit", which does just that. It's basically a framework that behaves similarly to CoreData, but uses StackOverflow.com (or any other stack exchange site) to retrieve information. Underneath, it's doing a lot to convert NSPredicate objects into a URL. You're also welcome to email me any specific questions you have.
I have an application where I would like to exchange information, managed via Core Data, between two iPhones.
First turning the Core Data object to an NSDictionary (something very simple that gets turned into NSData to be transferred).
My CoreData has 3 string attributes, 2 image attributes that are transformables.
I have looked through the NSDictionary API but have not had any luck with it, creating or adding the CoreData information to it.
Any help or sample code regarding this would be greatly appreciated.
I would recommend that you convert the Core Data objects to an intermediate format like JSON before pushing it over the wire. I have written up the code on how to transform NSManagedObject instances into and out of JSON in this other post:
JSON and Core Data on the iPhone
NSManagedObject doesn't conform to the NSCoding Protocol so you can't convert a managed object straight to data.
Instead, you just need to add a method to the managed object subclass that returns a dictionary with the instance attributes and then on the receiver side, use those to create a new managed object in the local context.
Edit:
From comments:
Currently I have for the sending
side..
NSData* data;
NSString *str0 = [NSString stringWithFormat:#"%#",[[person valueForKey:#"PersonName"] description]];
NSString *str1 = [NSString stringWithFormat:#"%#",[[person valueForKey:#"alias"] description]];
NSMutableDictionary *taskPrototype = [NSMutableDictionary dictionary];
[taskPrototype setObject:str0 forKey:#"PersonName"];
[taskPrototype setObject:str1 forKey:#"alias"];
data = ?????;
//I do not know what to put here... [self mySendDataToPeers:data];
on the receiving side I have...
NSMutableDictionary *trial = [[NSMutableDictionary alloc] initWithData:data];
NSString *str0a = ???? NSString *str1a = ????
//I dont know what to put after this to retrieve the values and keys from the dictionary
You would simply reverse the process to create a managed object on the receiver.
NSMutableDictionary *trial = [[NSMutableDictionary alloc] initWithData:data];
NSManagedObject *person=[NSEntityDescription insertNewObjectForEntityForName:#"PersonEntity" inManagedObjectContext:moc];
[person setValue:[trial objectForKey:#"PersonName"] forKey:#"PersonName"];
[person setValue:[trial objectForKey:#"alias"] forKey:#"alias"];
.. and you're done.