From d9fc311433c7a2bb1afa12c6ca31c3acca6b163f Mon Sep 17 00:00:00 2001 From: Daniel Hammond Date: Thu, 16 Jun 2011 21:17:14 -0400 Subject: [PATCH] Remove objects persisted to Core Dataduring postObject: when there is an error (Fix Issue #125) --- Code/CoreData/RKManagedObjectLoader.m | 7 +++++ Code/ObjectMapping/RKObjectLoader.h | 4 +++ Code/ObjectMapping/RKObjectLoader.m | 36 ++++++++++++----------- Specs/ObjectMapping/RKObjectManagerSpec.m | 16 ++++++++++ Specs/Server/server.rb | 6 ++++ 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/Code/CoreData/RKManagedObjectLoader.m b/Code/CoreData/RKManagedObjectLoader.m index 0959d348..01d7d835 100644 --- a/Code/CoreData/RKManagedObjectLoader.m +++ b/Code/CoreData/RKManagedObjectLoader.m @@ -116,4 +116,11 @@ return nil; } +// Overloaded to handle deleting an object orphaned by a failed postObject: +- (void)handleResponseError { + [super handleResponseError]; + [[self.objectStore managedObjectContext] deleteObject:[self.objectStore objectWithID:_targetObjectID]]; + [self.objectStore save]; +} + @end diff --git a/Code/ObjectMapping/RKObjectLoader.h b/Code/ObjectMapping/RKObjectLoader.h index 674aae62..530b887e 100644 --- a/Code/ObjectMapping/RKObjectLoader.h +++ b/Code/ObjectMapping/RKObjectLoader.h @@ -117,5 +117,9 @@ * Initialize a new object loader with an object mapper, a request, and a delegate */ - (id)initWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id)delegate; +/** + * Handle an error in the response preventing it from being mapped, called from -isResponseMappable + */ +- (void)handleResponseError; @end diff --git a/Code/ObjectMapping/RKObjectLoader.m b/Code/ObjectMapping/RKObjectLoader.m index d18f8349..e9b12c1f 100644 --- a/Code/ObjectMapping/RKObjectLoader.m +++ b/Code/ObjectMapping/RKObjectLoader.m @@ -198,23 +198,7 @@ return NO; } else if ([self.response isError]) { - - if ([self.response isServiceUnavailable]) { - [[NSNotificationCenter defaultCenter] postNotificationName:RKServiceDidBecomeUnavailableNotification object:self]; - } - - // Since we are mapping what we know to be an error response, we don't want to map the result back onto our - // target object - NSError* error = nil; - RKObjectMappingResult* result = [self mapResponseWithMappingProvider:self.objectManager.mappingProvider toObject:nil error:&error]; - if (result) { - error = [result asError]; - } else { - RKLogError(@"Encountered an error while attempting to map server side errors from payload: %@", [error localizedDescription]); - } - - [(NSObject*)_delegate objectLoader:self didFailWithError:error]; - + [self handleResponseError]; return NO; } else if ([self.response isSuccessful] && NO == [self canParseMIMEType:[self.response MIMEType]]) { RKLogWarning(@"Encountered unexpected response code: %d (MIME Type: %@)", self.response.statusCode, self.response.MIMEType); @@ -233,6 +217,24 @@ return YES; } +- (void)handleResponseError { + if ([self.response isServiceUnavailable]) { + [[NSNotificationCenter defaultCenter] postNotificationName:RKServiceDidBecomeUnavailableNotification object:self]; + } + + // Since we are mapping what we know to be an error response, we don't want to map the result back onto our + // target object + NSError* error = nil; + RKObjectMappingResult* result = [self mapResponseWithMappingProvider:self.objectManager.mappingProvider toObject:nil error:&error]; + if (result) { + error = [result asError]; + } else { + RKLogError(@"Encountered an error while attempting to map server side errors from payload: %@", [error localizedDescription]); + } + + [(NSObject*)_delegate objectLoader:self didFailWithError:error]; +} + #pragma mark - RKRequest & RKRequestDelegate methods // Invoked just before request hits the network diff --git a/Specs/ObjectMapping/RKObjectManagerSpec.m b/Specs/ObjectMapping/RKObjectManagerSpec.m index 69128073..437cba1e 100644 --- a/Specs/ObjectMapping/RKObjectManagerSpec.m +++ b/Specs/ObjectMapping/RKObjectManagerSpec.m @@ -91,6 +91,22 @@ assertThat(human.railsID, is(equalToInt(1))); } +- (void)itShouldDeleteACoreDataBackedTargetObjectOnError { + RKHuman* temporaryHuman = [[RKHuman alloc] initWithEntity:[NSEntityDescription entityForName:@"RKHuman" inManagedObjectContext:_objectManager.objectStore.managedObjectContext] insertIntoManagedObjectContext:_objectManager.objectStore.managedObjectContext]; + temporaryHuman.name = @"My Name"; + [_objectManager.objectStore save]; + + RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader]; + NSString* resourcePath = @"/humans/fail"; + RKObjectLoader* objectLoader = [_objectManager objectLoaderWithResourcePath:resourcePath delegate:loader]; + objectLoader.method = RKRequestMethodPOST; + objectLoader.targetObject = temporaryHuman; + [objectLoader send]; + [loader waitForResponse]; + + assertThat(temporaryHuman.managedObjectContext, is(equalTo(nil))); +} + // TODO: Move to Core Data specific spec file... - (void)itShouldLoadAHuman { RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader]; diff --git a/Specs/Server/server.rb b/Specs/Server/server.rb index 686fb87b..7f5253e4 100644 --- a/Specs/Server/server.rb +++ b/Specs/Server/server.rb @@ -46,6 +46,12 @@ class RestKit::SpecServer < Sinatra::Base {:human => {:name => "My Name", :id => 1, :website => "http://restkit.org/"}}.to_json end + post '/humans/fail' do + status 500 + content_type 'application/json' + send_file 'Specs/Server/../Fixtures/JSON/errors.json' + end + get '/humans/1' do status 200 content_type 'application/json'