Add support for deletion of mapped objects by predicate. closes #1109

This commit is contained in:
Blake Watters
2013-01-01 15:26:36 -05:00
parent 9dc08ca27b
commit 28887d3384
3 changed files with 133 additions and 1 deletions

View File

@@ -177,6 +177,17 @@
*/
- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName;
///------------------------------------
/// @name Flagging Objects for Deletion
///------------------------------------
/**
A predicate that identifies objects for the receiver's entity that are to be deleted from the local store.
This property provides support for local deletion of managed objects mapped as a 'tombstone' record from the source representation.
*/
@property (nonatomic, copy) NSPredicate *deletionPredicate;
///------------------------------------------
/// @name Retrieving Default Attribute Values
///------------------------------------------

View File

@@ -18,6 +18,7 @@
// limitations under the License.
//
#import <objc/runtime.h>
#import "RKManagedObjectMappingOperationDataSource.h"
#import "RKObjectMapping.h"
#import "RKEntityMapping.h"
@@ -34,6 +35,8 @@
extern NSString * const RKObjectMappingNestingAttributeKeyName;
static char kRKManagedObjectMappingOperationDataSourceAssociatedObjectKey;
id RKTransformedValueWithClass(id value, Class destinationType, NSValueTransformer *dateToStringValueTransformer);
NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings);
@@ -117,6 +120,58 @@ static NSDictionary *RKEntityIdentificationAttributesForEntityMappingWithReprese
return entityIdentifierAttributes;
}
@interface RKManagedObjectDeletionOperation : NSOperation
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
- (void)addEntityMapping:(RKEntityMapping *)entityMapping;
@end
@interface RKManagedObjectDeletionOperation ()
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong) NSMutableSet *entityMappings;
@end
@implementation RKManagedObjectDeletionOperation
- (id)initWithManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
self = [self init];
if (self) {
self.managedObjectContext = managedObjectContext;
self.entityMappings = [NSMutableSet new];
}
return self;
}
- (void)addEntityMapping:(RKEntityMapping *)entityMapping
{
if (! entityMapping.deletionPredicate) return;
[self.entityMappings addObject:entityMapping];
}
- (void)main
{
[self.managedObjectContext performBlockAndWait:^{
NSMutableSet *objectsToDelete = [NSMutableSet set];
for (RKEntityMapping *entityMapping in self.entityMappings) {
NSFetchRequest *fetchRequest = [NSFetchRequest alloc];
[fetchRequest setEntity:entityMapping.entity];
[fetchRequest setPredicate:entityMapping.deletionPredicate];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects) {
[objectsToDelete addObjectsFromArray:fetchedObjects];
}
}
for (NSManagedObject *managedObject in objectsToDelete) {
[self.managedObjectContext deleteObject:managedObject];
}
}];
}
@end
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent RKlcl_cRestKitCoreData
@@ -126,6 +181,7 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
@interface RKManagedObjectMappingOperationDataSource ()
@property (nonatomic, strong, readwrite) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, strong, readwrite) id<RKManagedObjectCaching> managedObjectCache;
@property (nonatomic, strong) NSMutableArray *deletionPredicates;
@end
@implementation RKManagedObjectMappingOperationDataSource
@@ -169,7 +225,7 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
"Unable to update existing object instances by identification attributes. Duplicate objects may be created.");
}
// If we have found the entity identifier attributes, try to find an existing instance to update
// If we have found the entity identification attributes, try to find an existing instance to update
NSEntityDescription *entity = [entityMapping entity];
NSManagedObject *managedObject = nil;
if ([entityIdentifierAttributes count]) {
@@ -246,6 +302,27 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
[operationQueue addOperation:operation];
RKLogTrace(@"Enqueued %@ dependent upon parent operation %@ to operation queue %@", operation, self.parentOperation, operationQueue);
}
// Handle tombstone deletion by predicate
if ([(RKEntityMapping *)mappingOperation.objectMapping deletionPredicate]) {
RKManagedObjectDeletionOperation *deletionOperation = nil;
if (self.parentOperation) {
// Attach a deletion operation for execution after the parent operation completes
deletionOperation = (RKManagedObjectDeletionOperation *)objc_getAssociatedObject(self.parentOperation, &kRKManagedObjectMappingOperationDataSourceAssociatedObjectKey);
if (! deletionOperation) {
deletionOperation = [[RKManagedObjectDeletionOperation alloc] initWithManagedObjectContext:self.managedObjectContext];
objc_setAssociatedObject(self.parentOperation, &kRKManagedObjectMappingOperationDataSourceAssociatedObjectKey, deletionOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[deletionOperation addDependency:self.parentOperation];
NSOperationQueue *operationQueue = self.operationQueue ?: [NSOperationQueue currentQueue];
[operationQueue addOperation:deletionOperation];
}
[deletionOperation addEntityMapping:(RKEntityMapping *)mappingOperation.objectMapping];
} else {
deletionOperation = [[RKManagedObjectDeletionOperation alloc] initWithManagedObjectContext:self.managedObjectContext];
[deletionOperation addEntityMapping:(RKEntityMapping *)mappingOperation.objectMapping];
[deletionOperation start];
}
}
}
return YES;

View File

@@ -1064,6 +1064,50 @@
}
- (void)testDeletionOfTombstoneRecords
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
RKEntityMapping *mapping = [[RKEntityMapping alloc] initWithEntity:entity];
[mapping addAttributeMappingsFromArray:@[ @"name" ]];
mapping.deletionPredicate = [NSPredicate predicateWithFormat:@"sex = %@", @"female"];
RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
human.sex = @"female";
RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext
cache:nil];
NSDictionary *representation = @{ @"name": @"Whatever" };
RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:representation destinationObject:human mapping:mapping];
operation.dataSource = dataSource;
NSError *error = nil;
BOOL success = [operation performMapping:&error];
assertThatBool(success, is(equalToBool(YES)));
expect([human isDeleted]).to.equal(YES);
}
- (void)testDeletionOfTombstoneRecordsInMapperOperation
{
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
RKEntityMapping *mapping = [[RKEntityMapping alloc] initWithEntity:entity];
[mapping addAttributeMappingsFromArray:@[ @"name" ]];
mapping.deletionPredicate = [NSPredicate predicateWithFormat:@"sex = %@", @"female"];
RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
human.sex = @"female";
RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext
cache:nil];
NSDictionary *representation = @{ @"name": @"Whatever" };
NSError *error = nil;
RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:representation mappingsDictionary:@{ [NSNull null]: mapping }];
mapperOperation.mappingOperationDataSource = dataSource;
BOOL success = [mapperOperation execute:&error];
assertThatBool(success, is(equalToBool(YES)));
expect([human isDeleted]).to.equal(YES);
}
// TODO: Import bencharmk utility somehow...
//- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithFetchRequestMappingCache
//{