Fix issue in which managed objects that are the children of non-managed objects were refetched and assigned as a collection rather than as a singular value. fixes #1118

This commit is contained in:
Blake Watters
2013-01-16 17:49:32 -05:00
parent 62af9290d4
commit bcbbd65fbe
2 changed files with 62 additions and 49 deletions

View File

@@ -25,6 +25,7 @@
#import "RKRequestOperationSubclass.h"
#import "NSManagedObjectContext+RKAdditions.h"
#import "NSManagedObject+RKAdditions.h"
#import "RKObjectUtilities.h"
// Graph visitor
#import "RKResponseDescriptor.h"
@@ -267,19 +268,6 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths)
}];
}
static void RKSetMappedValueForKeyPathInDictionary(id value, id rootKey, NSString *keyPath, NSMutableDictionary *dictionary)
{
NSCParameterAssert(value);
NSCParameterAssert(rootKey);
NSCParameterAssert(dictionary);
if (keyPath && ![keyPath isEqual:[NSNull null]]) {
id valueAtRootKey = [dictionary objectForKey:rootKey];
[valueAtRootKey setValue:value forKeyPath:keyPath];
} else {
[dictionary setObject:value forKey:rootKey];
}
}
// Precondition: Must be called from within the correct context
static NSManagedObject *RKRefetchManagedObjectInContext(NSManagedObject *managedObject, NSManagedObjectContext *managedObjectContext)
{
@@ -295,6 +283,41 @@ static NSManagedObject *RKRefetchManagedObjectInContext(NSManagedObject *managed
return refetchedObject;
}
static id RKRefetchedValueInManagedObjectContext(id value, NSManagedObjectContext *managedObjectContext)
{
if (! value) {
return value;
} else if ([value isKindOfClass:[NSArray class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableArray class]];
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableSet class]];
NSMutableSet *newValue = [[NSMutableSet alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSOrderedSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableOrderedSet class]];
NSMutableOrderedSet *newValue = [NSMutableOrderedSet orderedSet];
[(NSOrderedSet *)value enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue setObject:object atIndex:index];
}];
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSManagedObject class]]) {
value = RKRefetchManagedObjectInContext(value, managedObjectContext);
}
return value;
}
// Finds the key paths for all entity mappings in the graph whose parent objects are not other managed objects
static NSDictionary *RKDictionaryFromDictionaryWithManagedObjectsInVisitationsRefetchedInContext(NSDictionary *dictionaryOfManagedObjects, NSArray *visitations, NSManagedObjectContext *managedObjectContext)
{
@@ -309,41 +332,29 @@ static NSDictionary *RKDictionaryFromDictionaryWithManagedObjectsInVisitationsRe
// If keyPaths contains null, then the root object is a managed object and we only need to refetch it
NSSet *nonNestedKeyPaths = ([keyPaths containsObject:[NSNull null]]) ? [NSSet setWithObject:[NSNull null]] : RKSetByRemovingSubkeypathsFromSet(keyPaths);
NSDictionary *mappingResultsAtRootKey = [dictionaryOfManagedObjects objectForKey:rootKey];
NSDictionary *mappingResultsAtRootKey = [newDictionary objectForKey:rootKey];
for (NSString *keyPath in nonNestedKeyPaths) {
id value = [keyPath isEqual:[NSNull null]] ? mappingResultsAtRootKey : [mappingResultsAtRootKey valueForKeyPath:keyPath];
if (! value) {
continue;
} else if ([value isKindOfClass:[NSArray class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableArray class]];
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue addObject:object];
id value = nil;
if ([keyPath isEqual:[NSNull null]]) {
value = RKRefetchedValueInManagedObjectContext(mappingResultsAtRootKey, managedObjectContext);
if (value) [newDictionary setObject:value forKey:rootKey];
} else {
NSMutableArray *keyPathComponents = [[keyPath componentsSeparatedByString:@"."] mutableCopy];
NSString *destinationKey = [keyPathComponents lastObject];
[keyPathComponents removeLastObject];
id sourceObject = [keyPathComponents count] ? [mappingResultsAtRootKey valueForKeyPath:[keyPathComponents componentsJoinedByString:@"."]] : mappingResultsAtRootKey;
if (RKObjectIsCollection(sourceObject)) {
// This is a to-many relationship, we want to refetch each item at the keyPath
for (id nestedObject in sourceObject) {
// Refetch this object. Set it on the destination.
NSManagedObject *managedObject = [nestedObject valueForKey:destinationKey];
[nestedObject setValue:RKRefetchedValueInManagedObjectContext(managedObject, managedObjectContext) forKey:destinationKey];
}
} else {
// This is a singular relationship. We want to refetch the object and set it directly.
id valueToRefetch = [sourceObject valueForKey:destinationKey];
[sourceObject setValue:RKRefetchedValueInManagedObjectContext(valueToRefetch, managedObjectContext) forKey:destinationKey];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableSet class]];
NSMutableSet *newValue = [[NSMutableSet alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSOrderedSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableOrderedSet class]];
NSMutableOrderedSet *newValue = [NSMutableOrderedSet orderedSet];
[(NSOrderedSet *)value enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue setObject:object atIndex:index];
}];
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSManagedObject class]]) {
value = RKRefetchManagedObjectInContext(value, managedObjectContext);
}
if (value) {
RKSetMappedValueForKeyPathInDictionary(value, rootKey, keyPath, newDictionary);
}
}
}

View File

@@ -153,7 +153,7 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
expect(managedObjectRequestOperation.mappingResult).notTo.beNil();
NSArray *managedObjectContexts = [[managedObjectRequestOperation.mappingResult array] valueForKeyPath:@"@distinctUnionOfObjects.managedObjectContext"];
expect([managedObjectContexts count]).to.equal(1);
expect(managedObjectContexts[0]).to.equal(managedObjectStore.mainQueueManagedObjectContext);
expect(managedObjectContexts).to.equal([NSArray arrayWithObject:managedObjectStore.mainQueueManagedObjectContext]);
}
// 304 'Not Modified'
@@ -830,7 +830,7 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
[entityMapping addAttributeMappingsFromArray:@[ @"name" ]];
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"favorite_cat" toKeyPath:@"bestFriend" withMapping:entityMapping]];
[itemMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"items" toKeyPath:@"hasMany" withMapping:userMapping]];
[itemMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"items.human" toKeyPath:@"hasMany" withMapping:userMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:itemMapping pathPattern:nil keyPath:@"result" statusCodes:[NSIndexSet indexSetWithIndex:200]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/JSON/humans/has_many_with_to_one_relationship.json" relativeToURL:[RKTestFactory baseURL]]];
@@ -840,7 +840,9 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
expect(managedObjectRequestOperation.error).to.beNil();
RKMappableObject *result = [managedObjectRequestOperation.mappingResult.array lastObject];
RKTestUser *user = (RKTestUser *)result.hasMany.anyObject;
expect(user.bestFriend.class).to.equal(RKHuman.class);
NSLog(@"Examining result = %@, with result.hasMany = %@, user = %@ and user.bestFriend(%@) = %@", result, result.hasMany, user, [user.bestFriend class], user.bestFriend);
expect(user.bestFriend).to.beInstanceOf([RKHuman class]);
expect([user.bestFriend managedObjectContext]).to.equal(managedObjectStore.persistentStoreManagedObjectContext);
}
@end