Make deletion of newly inserted objects that fail validation configurable. closes #1281

This commit is contained in:
Blake Watters
2013-03-16 15:26:04 -04:00
parent 2f11970432
commit a0a0adf7d1
5 changed files with 39 additions and 5 deletions

View File

@@ -177,6 +177,19 @@
*/
- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName;
///-----------------------------
/// @name Configuring Validation
///-----------------------------
/**
A Boolean value that determines if newly created `NSManagedObject` instances mapped with the receiver should be discarded when they fail `validateForInsert:`.
This property allows for the deletion of managed objects that fail validation such that `NSManagedObjectContext` save will complete successfully. Typically an invalid managed object in the graph will result in a failure to save the `NSManagedObjectContext` due to an NSValidation error. In some cases it is desirable to persist only the subset of objects that pass validation and discard the invalid content rather than failing the entire operation. Setting this property to `YES` will result in the deletion of in any newly created `NSManagedObject` instances that fail to return `YES` when sent the `validateForInsert:` message.
**Default**: `NO`
*/
@property (nonatomic, assign) BOOL discardsInvalidObjectsOnInsert;
///------------------------------------
/// @name Flagging Objects for Deletion
///------------------------------------

View File

@@ -164,6 +164,7 @@ static BOOL entityIdentificationInferenceEnabled = YES;
self = [self initWithClass:objectClass];
if (self) {
self.entity = entity;
self.discardsInvalidObjectsOnInsert = NO;
if ([RKEntityMapping isEntityIdentificationInferenceEnabled]) self.identificationAttributes = RKIdentificationAttributesInferredFromEntity(entity);
}

View File

@@ -313,9 +313,10 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
- (BOOL)commitChangesForMappingOperation:(RKMappingOperation *)mappingOperation error:(NSError **)error
{
if ([mappingOperation.objectMapping isKindOfClass:[RKEntityMapping class]]) {
[self emitDeadlockWarningIfNecessary];
[self emitDeadlockWarningIfNecessary];
NSArray *connections = [(RKEntityMapping *)mappingOperation.objectMapping connections];
RKEntityMapping *entityMapping = (RKEntityMapping *)mappingOperation.objectMapping;
NSArray *connections = [entityMapping 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." };
NSError *localError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorNilManagedObjectCache userInfo:userInfo];
@@ -330,11 +331,11 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
*/
NSOperationQueue *operationQueue = self.operationQueue ?: [NSOperationQueue currentQueue];
__weak NSManagedObjectContext *weakContext = [(NSManagedObject *)mappingOperation.destinationObject managedObjectContext];
NSBlockOperation *deletionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSBlockOperation *deletionOperation = entityMapping.discardsInvalidObjectsOnInsert ? [NSBlockOperation blockOperationWithBlock:^{
[weakContext performBlockAndWait:^{
RKDeleteInvalidNewManagedObject(mappingOperation.destinationObject);
}];
}];
}] : nil;
// Add a dependency on the parent operation. If we are being mapped as part of a relationship, then the assignment of the mapped object to a parent may well fulfill the validation requirements. This ensures that the relationship mapping has completed before we evaluate the object for deletion.
if (self.parentOperation) [deletionOperation addDependency:self.parentOperation];

View File

@@ -1373,6 +1373,7 @@
NSDictionary *representation = @{ @"human": @{ @"name": @"Blake Watters", @"favoriteCatID": @(12345) }, @"cat": @{ @"railsID": @(12345) } };
RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat"
inManagedObjectStore:managedObjectStore];
catMapping.discardsInvalidObjectsOnInsert = YES;
RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectContext];
RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:cat mapping:catMapping];
NSOperationQueue *operationQueue = [NSOperationQueue new];

View File

@@ -1042,10 +1042,11 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
expect(fetchedObjects).to.haveCountOf(1);
}
- (void)testManagedObjectRequestOperationCompletesAndIgnoresInvalidObjects
- (void)testManagedObjectRequestOperationCompletesAndIgnoresInvalidObjectsWhenDiscardsInvalidObjectsOnInsertIsYES
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
postMapping.discardsInvalidObjectsOnInsert = YES;
[postMapping addAttributeMappingsFromArray:@[ @"title", @"body" ]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:postMapping pathPattern:nil keyPath:@"posts" statusCodes:[NSIndexSet indexSetWithIndex:200]];
@@ -1057,6 +1058,23 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
expect([managedObjectRequestOperation.mappingResult array]).to.haveCountOf(1);
}
- (void)testManagedObjectRequestOperationFailsWithValidationErrorWhenDiscardsInvalidObjectsOnInsertIsNO
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
RKEntityMapping *postMapping = [RKEntityMapping mappingForEntityForName:@"Post" inManagedObjectStore:managedObjectStore];
postMapping.discardsInvalidObjectsOnInsert = NO;
[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).notTo.beNil();
expect([managedObjectRequestOperation.error code]).to.equal(NSValidationMissingMandatoryPropertyError);
expect([managedObjectRequestOperation.error localizedDescription]).to.equal(@"The operation couldnt be completed. (Cocoa error 1570.)");
}
- (void)testThatSuccessfulCompletionSavesManagedObjectIfTargetObjectIsUnsavedEvenIfNoMappingWasPerformed
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];