From 0210424c6c05b8a620e6d1c2cbf00b6f1b158dac Mon Sep 17 00:00:00 2001 From: Sam Krishna Date: Mon, 3 Dec 2012 23:25:19 -0800 Subject: [PATCH 1/2] Added proper support for RKDynamicMapping as a container for RKEntityMappings inside of RKObjectManager. --- Code/Network/RKObjectManager.m | 10 ++++++++++ Code/ObjectMapping.h | 1 + 2 files changed, 11 insertions(+) diff --git a/Code/Network/RKObjectManager.m b/Code/Network/RKObjectManager.m index 413edb1a..76db9a8d 100644 --- a/Code/Network/RKObjectManager.m +++ b/Code/Network/RKObjectManager.m @@ -31,6 +31,7 @@ #import "RKPathMatcher.h" #import "RKMappingErrors.h" #import "RKPaginator.h" +#import "RKDynamicMapping.h" #if !__has_feature(objc_arc) #error RestKit must be built with ARC. @@ -92,6 +93,15 @@ static BOOL RKDoesArrayOfResponseDescriptorsContainEntityMapping(NSArray *respon if ([responseDescriptor.mapping isKindOfClass:[RKEntityMapping class]]) { return YES; } + + if ([responseDescriptor.mapping isKindOfClass:[RKDynamicMapping class]]) { + RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)responseDescriptor.mapping; + for (RKMapping *mapping in dynamicMapping.objectMappings) { + if ([mapping isKindOfClass:[RKEntityMapping class]]) { + return YES; + } + } + } } return NO; diff --git a/Code/ObjectMapping.h b/Code/ObjectMapping.h index 9bff91a5..3f818145 100644 --- a/Code/ObjectMapping.h +++ b/Code/ObjectMapping.h @@ -25,3 +25,4 @@ #import "RKObjectParameterization.h" #import "RKMappingResult.h" #import "RKMapperOperation.h" +#import "RKDynamicMapping.h" \ No newline at end of file From 997158e9e63038a03113ca48329f4d2cb48a0d5a Mon Sep 17 00:00:00 2001 From: Blake Watters Date: Fri, 7 Dec 2012 12:40:02 -0500 Subject: [PATCH 2/2] Fix issues with incorrect determination of the appropriate object request operation. fixes #1054, #1056 * Expands test coverage for the `appropriateObjectRequestOperationWithObject:method:path:parameters:` * Uses an object graph visitor to completely navigate the mapping graph, ensuring that an `RKEntityMapping` appearing at any nesting level will be correctly handled --- Code/Network/RKObjectManager.m | 80 ++++++++++++++++--- Code/Network/RKResponseDescriptor.m | 3 +- .../Logic/ObjectMapping/RKObjectManagerTest.m | 71 ++++++++++++++++ 3 files changed, 144 insertions(+), 10 deletions(-) diff --git a/Code/Network/RKObjectManager.m b/Code/Network/RKObjectManager.m index 76db9a8d..67aa1cd6 100644 --- a/Code/Network/RKObjectManager.m +++ b/Code/Network/RKObjectManager.m @@ -32,6 +32,7 @@ #import "RKMappingErrors.h" #import "RKPaginator.h" #import "RKDynamicMapping.h" +#import "RKRelationshipMapping.h" #if !__has_feature(objc_arc) #error RestKit must be built with ARC. @@ -82,24 +83,85 @@ static RKRequestDescriptor *RKRequestDescriptorFromArrayMatchingObject(NSArray * } /** - Returns `YES` if the given array of `RKResponseDescriptor` objects contains an `RKEntityMapping`. + Visits all mappings accessible via relationships or dynamic mapping in an object graph starting from a given mapping. + */ +@interface RKMappingGraphVisitor : NSObject + +@property (nonatomic, readonly) NSSet *mappings; + +- (id)initWithMapping:(RKMapping *)mapping; + +@end + +@interface RKMappingGraphVisitor () +@property (nonatomic, readwrite) NSMutableSet *mutableMappings; +@end + +@implementation RKMappingGraphVisitor + +- (id)initWithMapping:(RKMapping *)mapping +{ + self = [super init]; + if (self) { + self.mutableMappings = [NSMutableSet set]; + [self visitMapping:mapping]; + } + return self; +} + +- (NSSet *)mappings +{ + return self.mutableMappings; +} + +- (void)visitMapping:(RKMapping *)mapping +{ + if ([self.mappings containsObject:mapping]) return; + [self.mutableMappings addObject:mapping]; + + if ([mapping isKindOfClass:[RKDynamicMapping class]]) { + RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping; + for (RKMapping *nestedMapping in dynamicMapping.objectMappings) { + [self visitMapping:nestedMapping]; + } + } else if ([mapping isKindOfClass:[RKObjectMapping class]]) { + RKObjectMapping *objectMapping = (RKObjectMapping *)mapping; + for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) { + [self visitMapping:relationshipMapping.mapping]; + } + } +} + +@end + +/** + Returns `YES` if the given array of `RKResponseDescriptor` objects contains an `RKEntityMapping` anywhere in its object graph. @param responseDescriptor An array of `RKResponseDescriptor` objects. @return `YES` if the `mapping` property of any of the response descriptor objects in the given array is an instance of `RKEntityMapping`, else `NO`. */ static BOOL RKDoesArrayOfResponseDescriptorsContainEntityMapping(NSArray *responseDescriptors) { + // Visit all mappings accessible from the object graphs of all response descriptors + NSMutableSet *accessibleMappings = [NSMutableSet set]; for (RKResponseDescriptor *responseDescriptor in responseDescriptors) { - if ([responseDescriptor.mapping isKindOfClass:[RKEntityMapping class]]) { + if (! [accessibleMappings containsObject:responseDescriptor.mapping]) { + RKMappingGraphVisitor *graphVisitor = [[RKMappingGraphVisitor alloc] initWithMapping:responseDescriptor.mapping]; + [accessibleMappings unionSet:graphVisitor.mappings]; + } + } + + // Enumerate all mappings and search for an `RKEntityMapping` + for (RKMapping *mapping in accessibleMappings) { + if ([mapping isKindOfClass:[RKEntityMapping class]]) { return YES; } - - if ([responseDescriptor.mapping isKindOfClass:[RKDynamicMapping class]]) { - RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)responseDescriptor.mapping; - for (RKMapping *mapping in dynamicMapping.objectMappings) { - if ([mapping isKindOfClass:[RKEntityMapping class]]) { - return YES; - } + + if ([mapping isKindOfClass:[RKDynamicMapping class]]) { + RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping; + if ([dynamicMapping.objectMappings count] == 0) { + // Likely means that there is a representation block, assume `YES` + return YES; } } } diff --git a/Code/Network/RKResponseDescriptor.m b/Code/Network/RKResponseDescriptor.m index f9e908ba..c4593b74 100644 --- a/Code/Network/RKResponseDescriptor.m +++ b/Code/Network/RKResponseDescriptor.m @@ -24,6 +24,7 @@ // Cloned from AFStringFromIndexSet -- method should be non-static for reuse static NSString *RKStringFromIndexSet(NSIndexSet *indexSet) { + NSCParameterAssert(indexSet); NSMutableString *string = [NSMutableString string]; NSRange range = NSMakeRange([indexSet firstIndex], 1); @@ -80,7 +81,7 @@ static NSString *RKStringFromIndexSet(NSIndexSet *indexSet) { - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p pathPattern=%@ keyPath=%@ statusCodes=%@ : %@>", - NSStringFromClass([self class]), self, self.pathPattern, self.keyPath, RKStringFromIndexSet(self.statusCodes), self.mapping]; + NSStringFromClass([self class]), self, self.pathPattern, self.keyPath, self.statusCodes ? RKStringFromIndexSet(self.statusCodes) : self.statusCodes, self.mapping]; } - (BOOL)matchesPath:(NSString *)path diff --git a/Tests/Logic/ObjectMapping/RKObjectManagerTest.m b/Tests/Logic/ObjectMapping/RKObjectManagerTest.m index 0b16d06f..0309ecc4 100644 --- a/Tests/Logic/ObjectMapping/RKObjectManagerTest.m +++ b/Tests/Logic/ObjectMapping/RKObjectManagerTest.m @@ -25,6 +25,7 @@ #import "RKHuman.h" #import "RKCat.h" #import "RKObjectMapperTestModel.h" +#import "RKDynamicMapping.h" @interface RKSubclassedTestModel : RKObjectMapperTestModel @end @@ -526,6 +527,76 @@ expect(dictionary).to.equal(@{ @"subclassed": @{ @"age": @(30) } }); } +- (void)testThatResponseDescriptorWithUnmanagedMappingTriggersCreationOfObjectRequestOperation +{ + RKObjectMapping *vanillaMapping = [RKObjectMapping requestMapping]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:vanillaMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithDynamicMappingContainingEntityMappingsTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + [dynamicMapping setObjectMapping:humanMapping whenValueOfKeyPath:@"whatever" isEqualTo:@"whatever"]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithDynamicMappingUsingABlockTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) { + return humanMapping; + }]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithUnmanagedMappingContainingRelationshipMappingWithEntityMappingsTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [objectMapping addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:humanMapping]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objectMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithUnmanagedMappingContainingRelationshipMappingWithEntityMappingsDeepWithinObjectGraphTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [objectMapping addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:humanMapping]; + RKObjectMapping *objectMapping2 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [objectMapping2 addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:objectMapping]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objectMapping2 pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +// Test with relationship 2 levels deep +// Test with recursive relationships + //- (void)testShouldHandleConnectionFailures //{ // NSString *localBaseURL = [NSString stringWithFormat:@"http://127.0.0.1:3001"];