Skip to content

Commit

Permalink
Add support for deleting Core Data managed objects that fail validati…
Browse files Browse the repository at this point in the history
…on out of the mapping context. This enables you to silently drop mapping for managed objects that fail validation. fixes RestKit#691 closes RestKit#694
  • Loading branch information
blakewatters committed Jan 2, 2013
1 parent a30263b commit 879ffd7
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 3 deletions.
14 changes: 12 additions & 2 deletions Code/CoreData/RKManagedObjectMappingOperationDataSource.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#import "RKValueTransformers.h"
#import "RKRelationshipMapping.h"
#import "RKObjectUtilities.h"
#import "NSManagedObject+RKAdditions.h"

extern NSString * const RKObjectMappingNestingAttributeKeyName;

Expand Down Expand Up @@ -243,8 +244,7 @@ - (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRep
}

if (managedObject == nil) {
managedObject = [[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:self.managedObjectContext];
managedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
[managedObject setValuesForKeysWithDictionary:entityIdentifierAttributes];

if ([self.managedObjectCache respondsToSelector:@selector(didCreateObject:)]) {
Expand Down Expand Up @@ -276,6 +276,16 @@ - (BOOL)commitChangesForMappingOperation:(RKMappingOperation *)mappingOperation
if ([mappingOperation.objectMapping isKindOfClass:[RKEntityMapping class]]) {
[self emitDeadlockWarningIfNecessary];

// Validate unsaved objects
if ([mappingOperation.destinationObject isKindOfClass:[NSManagedObject class]] && [(NSManagedObject *)mappingOperation.destinationObject isNew]) {
NSError *validationError = nil;
if (! [(NSManagedObject *)mappingOperation.destinationObject validateForInsert:&validationError]) {
RKLogDebug(@"Unsaved NSManagedObject failed `validateForInsert:` - Deleting object from context: %@", validationError);
[self.managedObjectContext deleteObject:mappingOperation.destinationObject];
return YES;
}
}

NSArray *connections = [(RKEntityMapping *)mappingOperation.objectMapping connections];
if ([connections count] > 0 && self.managedObjectCache == nil) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Cannot map an entity mapping that contains connection mappings with a data source whose managed object cache is nil." };
Expand Down
5 changes: 4 additions & 1 deletion Code/Network/RKManagedObjectRequestOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,10 @@ - (void)willFinish
return;
}
success = [self saveContext:&error];
if (! success) self.error = error;
if (! success) {
self.error = error;
return;
}

// Refetch all managed objects nested at key paths within the results dictionary before returning
if (self.mappingResult) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ - (void)testThatMappingAnEntityMappingContainingAConnectionMappingWithANilManage
RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext
cache:nil];
id mockOperation = [OCMockObject mockForClass:[RKMappingOperation class]];
[[mockOperation stub] destinationObject];
[[[mockOperation stub] andReturn:mapping] objectMapping];
NSError *error = nil;
BOOL success = [dataSource commitChangesForMappingOperation:mockOperation error:&error];
Expand Down
32 changes: 32 additions & 0 deletions Tests/Logic/Network/RKManagedObjectRequestOperationTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@
#import "RKTestUser.h"
#import "RKMappingErrors.h"

@interface RKPost : NSManagedObject
@end

@implementation RKPost

- (BOOL)validateTitle:(id *)ioValue error:(NSError **)outError {
// Don't allow blank titles
if ((*ioValue == nil) || ([[(NSString*)*ioValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:@""])) {
return NO;
}

return YES;
}

@end

@interface RKManagedObjectRequestOperation ()
- (NSSet *)localObjectsFromFetchRequestsMatchingRequestURL:(NSError **)error;
@end
Expand Down Expand Up @@ -447,6 +463,7 @@ - (void)testThatDeletionOfOrphanedObjectsCanBeSuppressedByPredicate
[tagOnDiferentObject setValue:@"orphaned" forKey:@"name"];

NSManagedObject *otherPost = [NSEntityDescription insertNewObjectForEntityForName:@"Post" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[otherPost setValue:@"Title" forKey:@"title"];
[otherPost setValue:[NSSet setWithObject:tagOnDiferentObject] forKey:@"tags"];

RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
Expand Down Expand Up @@ -738,4 +755,19 @@ - (void)testThatMappingObjectsWithTheSameIdentificationAttributesAcrossTwoObject
expect(fetchedObjects).to.haveCountOf(1);
}

- (void)testManagedObjectRequestOperationCompletesAndIgnoresInvalidObjects
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
[postMapping addAttributeMappingsFromArray:@[ @"title", @"body" ]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:postMapping pathPattern:nil keyPath:@"posts" statusCodes:[NSIndexSet indexSetWithIndex:200]];

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/posts_with_invalid.json" relativeToURL:[RKTestFactory baseURL]]];
RKManagedObjectRequestOperation *managedObjectRequestOperation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
managedObjectRequestOperation.managedObjectContext = managedObjectStore.persistentStoreManagedObjectContext;
[managedObjectRequestOperation start];
expect(managedObjectRequestOperation.error).to.beNil();
expect([managedObjectRequestOperation.mappingResult array]).to.haveCountOf(1);
}

@end
Binary file modified Tests/Models/Data Model.xcdatamodel/elements
Binary file not shown.
Binary file modified Tests/Models/Data Model.xcdatamodel/layout
Binary file not shown.
5 changes: 5 additions & 0 deletions Tests/Server/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ def render_fixture(path, options = {})
content_type 'application/json'
{ :posts => [{:title => 'Post Title', :body => 'Some body.', :tags => [{ :name => 'development' }, { :name => 'restkit' }] }] }.to_json
end

get '/posts_with_invalid.json' do
content_type 'application/json'
{ :posts => [{:title => 'Post Title', :body => 'Some body.'}, {:title => '', :body => 'Some body.'} ] }.to_json
end

# start the server if ruby file executed directly
run! if app_file == $0
Expand Down

0 comments on commit 879ffd7

Please sign in to comment.