Skip to content

Commit a471bdd

Browse files
committed
Add support for refetching NSManagedObject instances mapped at any point in the object graph. refs RestKit#1066
1 parent 69c65ef commit a471bdd

File tree

2 files changed

+128
-13
lines changed

2 files changed

+128
-13
lines changed

Code/Network/RKManagedObjectRequestOperation.m

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,71 @@
2525
#import "RKRequestOperationSubclass.h"
2626
#import "NSManagedObjectContext+RKAdditions.h"
2727

28+
// Graph visitor
29+
#import "RKResponseDescriptor.h"
30+
#import "RKEntityMapping.h"
31+
#import "RKDynamicMapping.h"
32+
#import "RKRelationshipMapping.h"
33+
2834
// Set Logging Component
2935
#undef RKLogComponent
3036
#define RKLogComponent RKlcl_cRestKitCoreData
3137

38+
@interface RKNestedManagedObjectKeyPathMappingGraphVisitor : NSObject
39+
40+
@property (nonatomic, readonly) NSSet *keyPaths;
41+
42+
- (id)initWithResponseDescriptors:(NSArray *)responseDescriptors;
43+
44+
@end
45+
46+
@interface RKNestedManagedObjectKeyPathMappingGraphVisitor ()
47+
@property (nonatomic, strong) NSMutableSet *mutableKeyPaths;
48+
@end
49+
50+
@implementation RKNestedManagedObjectKeyPathMappingGraphVisitor
51+
52+
- (id)initWithResponseDescriptors:(NSArray *)responseDescriptors
53+
{
54+
self = [self init];
55+
if (self) {
56+
self.mutableKeyPaths = [NSMutableSet set];
57+
for (RKResponseDescriptor *responseDescriptor in responseDescriptors) {
58+
[self visitMapping:responseDescriptor.mapping atKeyPath:responseDescriptor.keyPath ?: @""];
59+
}
60+
}
61+
return self;
62+
}
63+
64+
- (NSSet *)keyPaths
65+
{
66+
return self.mutableKeyPaths;
67+
}
68+
69+
- (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
70+
{
71+
if ([self.keyPaths containsObject:keyPath]) return;
72+
73+
if ([mapping isKindOfClass:[RKEntityMapping class]]) {
74+
[self.mutableKeyPaths addObject:keyPath];
75+
} else {
76+
if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
77+
RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping;
78+
for (RKMapping *nestedMapping in dynamicMapping.objectMappings) {
79+
[self visitMapping:nestedMapping atKeyPath:keyPath];
80+
}
81+
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
82+
RKObjectMapping *objectMapping = (RKObjectMapping *)mapping;
83+
for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) {
84+
NSString *nestedKeyPath = [@[ keyPath, relationshipMapping.destinationKeyPath ] componentsJoinedByString:@"."];
85+
[self visitMapping:relationshipMapping.mapping atKeyPath:nestedKeyPath];
86+
}
87+
}
88+
}
89+
}
90+
91+
@end
92+
3293
NSArray *RKArrayOfFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSURL *URL)
3394
{
3495
NSMutableArray *fetchRequests = [NSMutableArray array];
@@ -40,16 +101,17 @@
40101
return fetchRequests;
41102
}
42103

43-
// RKManagedObjectOrArrayOfManagedObjectsInContext(id managedObjectOrArrayOfManagedObjects, NSManagedObjectContext *managedObjectContext);
44-
// Find the key paths for all entity mappings in the graph whose parent objects are not other managed objects
45-
46-
static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManagedObjects(NSDictionary *dictionaryOfManagedObjects, NSManagedObjectContext *managedObjectContext)
104+
// Finds the key paths for all entity mappings in the graph whose parent objects are not other managed objects
105+
static NSDictionary *RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext(NSDictionary *dictionaryOfManagedObjects, NSSet *keyPaths, NSManagedObjectContext *managedObjectContext)
47106
{
48-
NSMutableDictionary *newDictionary = [[NSMutableDictionary alloc] initWithCapacity:[dictionaryOfManagedObjects count]];
107+
NSMutableDictionary *newDictionary = [dictionaryOfManagedObjects mutableCopy];
49108
[managedObjectContext performBlockAndWait:^{
50109
__block NSError *error = nil;
51-
[dictionaryOfManagedObjects enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
110+
111+
for (NSString *keyPath in keyPaths) {
112+
id value = [dictionaryOfManagedObjects valueForKeyPath:keyPath];
52113
if ([value isKindOfClass:[NSArray class]]) {
114+
BOOL isMutable = [value isKindOfClass:[NSMutableArray class]];
53115
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity:[value count]];
54116
for (__strong id object in value) {
55117
if ([object isKindOfClass:[NSManagedObject class]]) {
@@ -59,14 +121,38 @@
59121

60122
[newValue addObject:object];
61123
}
62-
value = [newValue copy];
124+
value = (isMutable) ? newValue : [newValue copy];
125+
} else if ([value isKindOfClass:[NSSet class]]) {
126+
BOOL isMutable = [value isKindOfClass:[NSMutableSet class]];
127+
NSMutableSet *newValue = [[NSMutableSet alloc] initWithCapacity:[value count]];
128+
for (__strong id object in value) {
129+
if ([object isKindOfClass:[NSManagedObject class]]) {
130+
object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
131+
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
132+
}
133+
134+
[newValue addObject:object];
135+
}
136+
value = (isMutable) ? newValue : [newValue copy];
137+
} else if ([value isKindOfClass:[NSOrderedSet class]]) {
138+
BOOL isMutable = [value isKindOfClass:[NSMutableOrderedSet class]];
139+
NSMutableOrderedSet *newValue = [NSMutableOrderedSet orderedSet];
140+
[(NSOrderedSet *)value enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
141+
if ([object isKindOfClass:[NSManagedObject class]]) {
142+
object = [managedObjectContext existingObjectWithID:[object objectID] error:&error];
143+
NSCAssert(object, @"Failed to find existing object with ID %@ in context %@: %@", [object objectID], managedObjectContext, error);
144+
}
145+
146+
[newValue setObject:object atIndex:index];
147+
}];
148+
value = (isMutable) ? newValue : [newValue copy];
63149
} else if ([value isKindOfClass:[NSManagedObject class]]) {
64150
value = [managedObjectContext existingObjectWithID:[value objectID] error:&error];
65151
NSCAssert(value, @"Failed to find existing object with ID %@ in context %@: %@", [value objectID], managedObjectContext, error);
66152
}
67153

68-
[newDictionary setValue:value forKey:key];
69-
}];
154+
[newDictionary setValue:value forKeyPath:keyPath];
155+
}
70156
}];
71157

72158
return newDictionary;
@@ -343,9 +429,12 @@ - (void)willFinish
343429
success = [self saveContext:&error];
344430
if (! success) self.error = error;
345431

346-
// Refetch the mapping results from the externally configured context
347-
NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryOfManagedObjectsInContextFromDictionaryOfManagedObjects([self.mappingResult dictionary], self.managedObjectContext);
348-
self.mappingResult = [[RKMappingResult alloc] initWithDictionary:resultsDictionaryFromOriginalContext];
432+
// Refetch all managed objects nested at key paths within the results dictionary before returning
433+
if (self.mappingResult) {
434+
RKNestedManagedObjectKeyPathMappingGraphVisitor *visitor = [[RKNestedManagedObjectKeyPathMappingGraphVisitor alloc] initWithResponseDescriptors:self.responseDescriptors];
435+
NSDictionary *resultsDictionaryFromOriginalContext = RKDictionaryFromDictionaryWithManagedObjectsAtKeyPathsRefetchedInContext([self.mappingResult dictionary], visitor.keyPaths, self.managedObjectContext);
436+
self.mappingResult = [[RKMappingResult alloc] initWithDictionary:resultsDictionaryFromOriginalContext];
437+
}
349438
}
350439

351440
@end

Tests/Logic/Network/RKManagedObjectRequestOperationTest.m

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import "RKManagedObjectRequestOperation.h"
1111
#import "RKEntityMapping.h"
1212
#import "RKHuman.h"
13+
#import "RKTestUser.h"
1314
#import "RKMappingErrors.h"
1415

1516
@interface RKManagedObjectRequestOperation ()
@@ -147,7 +148,7 @@ - (void)testThatManagedObjectsAreFetchedWhenHandlingANotModifiedResponse
147148
expect(managedObjectRequestOperation.mappingResult).notTo.beNil();
148149
NSArray *mappedObjects = [managedObjectRequestOperation.mappingResult array];
149150
expect(mappedObjects).to.haveCountOf(1);
150-
expect(mappedObjects[0]).to.equal(human);
151+
expect([mappedObjects[0] objectID]).to.equal([human objectID]);
151152
}
152153

153154
- (void)testThatInvalidObjectFailingManagedObjectContextSaveFailsOperation
@@ -271,4 +272,29 @@ - (void)testDeletionOfObjectWithResponseDescriptorMappingResponseByKeyPath
271272
expect([human hasBeenDeleted]).to.equal(YES);
272273
}
273274

275+
- (void)testThatManagedObjectMappedAsTheRelationshipOfNonManagedObjectsAreRefetchedFromTheParentContext
276+
{
277+
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
278+
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]];
279+
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
280+
[entityMapping addAttributeMappingsFromArray:@[ @"name" ]];
281+
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"favorite_cat" toKeyPath:@"friends" withMapping:entityMapping]];
282+
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:nil keyPath:@"human" statusCodes:[NSIndexSet indexSetWithIndex:200]];
283+
284+
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/JSON/humans/with_to_one_relationship.json" relativeToURL:[RKTestFactory baseURL]]];
285+
RKManagedObjectRequestOperation *managedObjectRequestOperation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
286+
managedObjectRequestOperation.managedObjectContext = managedObjectStore.persistentStoreManagedObjectContext;
287+
[managedObjectRequestOperation start];
288+
expect(managedObjectRequestOperation.error).to.beNil();
289+
expect([managedObjectRequestOperation.mappingResult array]).to.haveCountOf(1);
290+
RKTestUser *testUser = [managedObjectRequestOperation.mappingResult firstObject];
291+
expect([[testUser.friends lastObject] managedObjectContext]).to.equal(managedObjectStore.persistentStoreManagedObjectContext);
292+
}
293+
294+
// TODO: Test with dynamic mapping
295+
// TODO: Test with nil root key path
296+
// TODO: Test with NSSet
297+
// TODO: Test with NSOrderedSet
298+
// TODO: Test with NSSet
299+
274300
@end

0 commit comments

Comments
 (0)