diff --git a/Code/ObjectMapping/RKMappingOperation.m b/Code/ObjectMapping/RKMappingOperation.m index d2114f93..a68fcc48 100644 --- a/Code/ObjectMapping/RKMappingOperation.m +++ b/Code/ObjectMapping/RKMappingOperation.m @@ -198,6 +198,15 @@ static void RKSetValueForObject(id value, id destinationObject) } } +// Returns YES if there is a value present for at least one key path in the given collection +static BOOL RKObjectContainsValueForKeyPaths(id representation, NSArray *keyPaths) +{ + for (NSString *keyPath in keyPaths) { + if ([representation valueForKeyPath:keyPath]) return YES; + } + return NO; +} + @interface RKMappingOperation () @property (nonatomic, strong, readwrite) RKMapping *mapping; @property (nonatomic, strong, readwrite) id sourceObject; @@ -599,8 +608,26 @@ static void RKSetValueForObject(id value, id destinationObject) for (RKRelationshipMapping *relationshipMapping in [self relationshipMappings]) { if ([self isCancelled]) return NO; - // The nil source keyPath indicates that we want to map directly from the parent representation - id value = (relationshipMapping.sourceKeyPath == nil) ? self.sourceObject : [self.sourceObject valueForKeyPath:relationshipMapping.sourceKeyPath]; + id value = nil; + if (relationshipMapping.sourceKeyPath) { + value = [self.sourceObject valueForKeyPath:relationshipMapping.sourceKeyPath]; + } else { + // The nil source keyPath indicates that we want to map directly from the parent representation + value = self.sourceObject; + RKObjectMapping *objectMapping = nil; + + if ([relationshipMapping.mapping isKindOfClass:[RKObjectMapping class]]) { + objectMapping = (RKObjectMapping *)relationshipMapping.mapping; + } else if ([relationshipMapping.mapping isKindOfClass:[RKDynamicMapping class]]) { + objectMapping = [(RKDynamicMapping *)relationshipMapping.mapping objectMappingForRepresentation:value]; + } + + if (! objectMapping) continue; // Mapping declined + NSArray *propertyKeyPaths = [relationshipMapping valueForKeyPath:@"mapping.propertyMappings.sourceKeyPath"]; + if (! RKObjectContainsValueForKeyPaths(value, propertyKeyPaths)) { + continue; + } + } // Track that we applied this mapping [mappingsApplied addObject:relationshipMapping]; diff --git a/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m b/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m index 582ae516..8d3a11b1 100644 --- a/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m +++ b/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m @@ -2414,4 +2414,21 @@ expect(user.coordinate.longitude).to.equal(200.5); } +- (void)testThatAggregatedRelationshipMappingsAreOnlyAppliedIfThereIsAtLeastOneValueInTheRepresentation +{ + NSDictionary *objectRepresentation = @{ @"name": @"Blake" }; + RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; + [userMapping addAttributeMappingsFromArray:@[ @"name" ]]; + RKObjectMapping *coordinateMapping = [RKObjectMapping mappingForClass:[RKTestCoordinate class]]; + [coordinateMapping addAttributeMappingsFromArray:@[ @"latitude", @"longitude" ]]; + [userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:@"coordinate" withMapping:coordinateMapping]]; + RKTestUser *user = [RKTestUser new]; + RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:objectRepresentation destinationObject:user mapping:userMapping]; + RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; + mappingOperation.dataSource = dataSource; + [mappingOperation start]; + expect(mappingOperation.error).to.beNil(); + expect(user.coordinate).to.beNil(); +} + @end