Fix issue where NSManagedObject instances that are invalid due to missing require relationship when finished mapping, but are made valid by connected relationships are deleted prematurely. fixes #1179

This commit is contained in:
Blake Watters
2013-01-29 11:44:15 -05:00
parent 25e15508f6
commit 4f442b142f
4 changed files with 61 additions and 13 deletions

View File

@@ -123,6 +123,20 @@ static id RKMutableCollectionValueWithObjectForKeyPath(id object, NSString *keyP
return nil;
}
static BOOL RKDeleteInvalidNewManagedObject(NSManagedObject *managedObject)
{
if ([managedObject isKindOfClass:[NSManagedObject class]] && [managedObject managedObjectContext] && [managedObject isNew]) {
NSError *validationError = nil;
if (! [managedObject validateForInsert:&validationError]) {
RKLogDebug(@"Unsaved NSManagedObject failed `validateForInsert:` - Deleting object from context: %@", validationError);
[managedObject.managedObjectContext deleteObject:managedObject];
return YES;
}
}
return NO;
}
@interface RKManagedObjectDeletionOperation : NSOperation
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
@@ -296,17 +310,7 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
- (BOOL)commitChangesForMappingOperation:(RKMappingOperation *)mappingOperation error:(NSError **)error
{
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;
}
}
[self emitDeadlockWarningIfNecessary];
NSArray *connections = [(RKEntityMapping *)mappingOperation.objectMapping connections];
if ([connections count] > 0 && self.managedObjectCache == nil) {
@@ -315,7 +319,16 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
if (error) *error = localError;
return NO;
}
// Delete the object immediately if there are no connections that may make it valid
if ([connections count] == 0 && RKDeleteInvalidNewManagedObject(mappingOperation.destinationObject)) return YES;
// Attempt to establish the connections and delete the object if its invalid once we are done
NSOperationQueue *operationQueue = self.operationQueue ?: [NSOperationQueue currentQueue];
NSBlockOperation *deletionOperation = [NSBlockOperation blockOperationWithBlock:^{
RKDeleteInvalidNewManagedObject(mappingOperation.destinationObject);
}];
for (RKConnectionDescription *connection in connections) {
RKRelationshipConnectionOperation *operation = [[RKRelationshipConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject connection:connection managedObjectCache:self.managedObjectCache];
[operation setConnectionBlock:^(RKRelationshipConnectionOperation *operation, id connectedValue) {
@@ -330,10 +343,13 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
}
}];
if (self.parentOperation) [operation addDependency:self.parentOperation];
NSOperationQueue *operationQueue = self.operationQueue ?: [NSOperationQueue currentQueue];
[deletionOperation addDependency:operation];
[operationQueue addOperation:operation];
RKLogTrace(@"Enqueued %@ dependent upon parent operation %@ to operation queue %@", operation, self.parentOperation, operationQueue);
}
// Enqueue our deletion operation for execution after all the connections
[operationQueue addOperation:deletionOperation];
// Handle tombstone deletion by predicate
if ([(RKEntityMapping *)mappingOperation.objectMapping deletionPredicate]) {