mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-24 04:46:01 +08:00
Add support for refetching NSManagedObject instances mapped at any point in the object graph. refs #1066
This commit is contained in:
@@ -25,10 +25,71 @@
|
||||
#import "RKRequestOperationSubclass.h"
|
||||
#import "NSManagedObjectContext+RKAdditions.h"
|
||||
|
||||
// Graph visitor
|
||||
#import "RKResponseDescriptor.h"
|
||||
#import "RKEntityMapping.h"
|
||||
#import "RKDynamicMapping.h"
|
||||
#import "RKRelationshipMapping.h"
|
||||
|
||||
// Set Logging Component
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent RKlcl_cRestKitCoreData
|
||||
|
||||
@interface RKNestedManagedObjectKeyPathMappingGraphVisitor : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSSet *keyPaths;
|
||||
|
||||
- (id)initWithResponseDescriptors:(NSArray *)responseDescriptors;
|
||||
|
||||
@end
|
||||
|
||||
@interface RKNestedManagedObjectKeyPathMappingGraphVisitor ()
|
||||
@property (nonatomic, strong) NSMutableSet *mutableKeyPaths;
|
||||
@end
|
||||
|
||||
@implementation RKNestedManagedObjectKeyPathMappingGraphVisitor
|
||||
|
||||
- (id)initWithResponseDescriptors:(NSArray *)responseDescriptors
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
self.mutableKeyPaths = [NSMutableSet set];
|
||||
for (RKResponseDescriptor *responseDescriptor in responseDescriptors) {
|
||||
[self visitMapping:responseDescriptor.mapping atKeyPath:responseDescriptor.keyPath ?: @""];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSSet *)keyPaths
|
||||
{
|
||||
return self.mutableKeyPaths;
|
||||
}
|
||||
|
||||
- (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
|
||||
{
|
||||
if ([self.keyPaths containsObject:keyPath]) return;
|
||||
|
||||
if ([mapping isKindOfClass:[RKEntityMapping class]]) {
|
||||
[self.mutableKeyPaths addObject:keyPath];
|
||||
} else {
|
||||
if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
|
||||
RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping;
|
||||
for (RKMapping *nestedMapping in dynamicMapping.objectMappings) {
|
||||
[self visitMapping:nestedMapping atKeyPath:keyPath];
|
||||
}
|
||||
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
|
||||
RKObjectMapping *objectMapping = (RKObjectMapping *)mapping;
|
||||
for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) {
|
||||
NSString *nestedKeyPath = [@[ keyPath, relationshipMapping.destinationKeyPath ] componentsJoinedByString:@"."];
|
||||
[self visitMapping:relationshipMapping.mapping atKeyPath:nestedKeyPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSArray *RKArrayOfFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSURL *URL)
|
||||
{
|
||||
NSMutableArray *fetchRequests = [NSMutableArray array];
|
||||
@@ -40,16 +101,17 @@ NSArray *RKArrayOfFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSU
|
||||
return fetchRequests;
|
||||
}
|
||||
|
||||
// RKManagedObjectOrArrayOfManagedObjectsInContext(id managedObjectOrArrayOfManagedObjects, NSManagedObjectContext *managedObjectContext);
|
||||
// Find the key paths for all entity mappings in the graph whose parent objects are not other managed objects
|
||||
|
||||
static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManagedObjects(NSDictionary *dictionaryOfManagedObjects, NSManagedObjectContext *managedObjectContext)
|
||||
// Finds the key paths for all entity mappings in the graph whose parent objects are not other managed objects
|
||||
static NSDictionary *RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext(NSDictionary *dictionaryOfManagedObjects, NSSet *keyPaths, NSManagedObjectContext *managedObjectContext)
|
||||
{
|
||||
NSMutableDictionary *newDictionary = [[NSMutableDictionary alloc] initWithCapacity:[dictionaryOfManagedObjects count]];
|
||||
NSMutableDictionary *newDictionary = [dictionaryOfManagedObjects mutableCopy];
|
||||
[managedObjectContext performBlockAndWait:^{
|
||||
__block NSError *error = nil;
|
||||
[dictionaryOfManagedObjects enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
|
||||
|
||||
for (NSString *keyPath in keyPaths) {
|
||||
id value = [dictionaryOfManagedObjects valueForKeyPath:keyPath];
|
||||
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]]) {
|
||||
@@ -59,14 +121,38 @@ static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManage
|
||||
|
||||
[newValue addObject:object];
|
||||
}
|
||||
value = [newValue copy];
|
||||
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 = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
|
||||
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
|
||||
}
|
||||
|
||||
[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 = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
|
||||
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
|
||||
}
|
||||
|
||||
[newValue setObject:object atIndex:index];
|
||||
}];
|
||||
value = (isMutable) ? newValue : [newValue copy];
|
||||
} else if ([value isKindOfClass:[NSManagedObject class]]) {
|
||||
value = [managedObjectContext existingObjectWithID:[value objectID] error:&error];
|
||||
NSCAssert(value, @"Failed to find existing object with ID %@ in context %@: %@", [value objectID], managedObjectContext, error);
|
||||
}
|
||||
|
||||
[newDictionary setValue:value forKey:key];
|
||||
}];
|
||||
[newDictionary setValue:value forKeyPath:keyPath];
|
||||
}
|
||||
}];
|
||||
|
||||
return newDictionary;
|
||||
@@ -343,9 +429,12 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re
|
||||
success = [self saveContext:&error];
|
||||
if (! success) self.error = error;
|
||||
|
||||
// Refetch the mapping results from the externally configured context
|
||||
NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryOfManagedObjectsInContextFromDictionaryOfManagedObjects([self.mappingResult dictionary], self.managedObjectContext);
|
||||
self.mappingResult = [[RKMappingResult alloc] initWithDictionary:resultsDictionaryFromOriginalContext];
|
||||
// Refetch all managed objects nested at key paths within the results dictionary before returning
|
||||
if (self.mappingResult) {
|
||||
RKNestedManagedObjectKeyPathMappingGraphVisitor *visitor = [[RKNestedManagedObjectKeyPathMappingGraphVisitor alloc] initWithResponseDescriptors:self.responseDescriptors];
|
||||
NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext([self.mappingResult dictionary], visitor.keyPaths, self.managedObjectContext);
|
||||
self.mappingResult = [[RKMappingResult alloc] initWithDictionary:resultsDictionaryFromOriginalContext];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "RKManagedObjectRequestOperation.h"
|
||||
#import "RKEntityMapping.h"
|
||||
#import "RKHuman.h"
|
||||
#import "RKTestUser.h"
|
||||
#import "RKMappingErrors.h"
|
||||
|
||||
@interface RKManagedObjectRequestOperation ()
|
||||
@@ -147,7 +148,7 @@
|
||||
expect(managedObjectRequestOperation.mappingResult).notTo.beNil();
|
||||
NSArray *mappedObjects = [managedObjectRequestOperation.mappingResult array];
|
||||
expect(mappedObjects).to.haveCountOf(1);
|
||||
expect(mappedObjects[0]).to.equal(human);
|
||||
expect([mappedObjects[0] objectID]).to.equal([human objectID]);
|
||||
}
|
||||
|
||||
- (void)testThatInvalidObjectFailingManagedObjectContextSaveFailsOperation
|
||||
@@ -271,4 +272,29 @@
|
||||
expect([human hasBeenDeleted]).to.equal(YES);
|
||||
}
|
||||
|
||||
- (void)testThatManagedObjectMappedAsTheRelationshipOfNonManagedObjectsAreRefetchedFromTheParentContext
|
||||
{
|
||||
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
|
||||
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
|
||||
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
|
||||
[entityMapping addAttributeMappingsFromArray:@[ @"name" ]];
|
||||
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"favorite_cat" toKeyPath:@"friends" withMapping:entityMapping]];
|
||||
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:nil keyPath:@"human" statusCodes:[NSIndexSet indexSetWithIndex:200]];
|
||||
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/JSON/humans/with_to_one_relationship.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);
|
||||
RKTestUser *testUser = [managedObjectRequestOperation.mappingResult firstObject];
|
||||
expect([[testUser.friends lastObject] managedObjectContext]).to.equal(managedObjectStore.persistentStoreManagedObjectContext);
|
||||
}
|
||||
|
||||
// TODO: Test with dynamic mapping
|
||||
// TODO: Test with nil root key path
|
||||
// TODO: Test with NSSet
|
||||
// TODO: Test with NSOrderedSet
|
||||
// TODO: Test with NSSet
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user