mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-01 22:42:51 +08:00
NSDateFormatters, added a preferredDateFormatter for use when serializing dates to strings, replaced the use of the description method for date encoding to strings with invocation of the preferredDateFormatter, added new attribute transformation strategy from NSDate -> NSString properties (also using the preferred date formatter), and provided customization support for date handling globally and on a per-mapping basis. closes #200, closes #313, closes #309, closes #308
506 lines
26 KiB
Objective-C
506 lines
26 KiB
Objective-C
|
|
//
|
|
// RKObjectMappingOperation.m
|
|
// RestKit
|
|
//
|
|
// Created by Blake Watters on 4/30/11.
|
|
// Copyright 2011 Two Toasters. All rights reserved.
|
|
//
|
|
|
|
#import <objc/message.h>
|
|
#import "RKObjectMappingOperation.h"
|
|
#import "RKObjectMapperError.h"
|
|
#import "RKObjectPropertyInspector.h"
|
|
#import "RKObjectRelationshipMapping.h"
|
|
#import "RKObjectMapper.h"
|
|
#import "../Support/Errors.h"
|
|
#import "../Support/RKLog.h"
|
|
|
|
// Set Logging Component
|
|
#undef RKLogComponent
|
|
#define RKLogComponent lcl_cRestKitObjectMapping
|
|
|
|
extern NSString* const RKObjectMappingNestingAttributeKeyName;
|
|
|
|
// Temporary home for object equivalancy tests
|
|
BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue) {
|
|
NSCAssert(sourceValue, @"Expected sourceValue not to be nil");
|
|
NSCAssert(destinationValue, @"Expected destinationValue not to be nil");
|
|
|
|
SEL comparisonSelector;
|
|
if ([sourceValue isKindOfClass:[NSString class]]) {
|
|
comparisonSelector = @selector(isEqualToString:);
|
|
} else if ([sourceValue isKindOfClass:[NSNumber class]]) {
|
|
comparisonSelector = @selector(isEqualToNumber:);
|
|
} else if ([sourceValue isKindOfClass:[NSDate class]]) {
|
|
comparisonSelector = @selector(isEqualToDate:);
|
|
} else if ([sourceValue isKindOfClass:[NSArray class]]) {
|
|
comparisonSelector = @selector(isEqualToArray:);
|
|
} else if ([sourceValue isKindOfClass:[NSDictionary class]]) {
|
|
comparisonSelector = @selector(isEqualToDictionary:);
|
|
} else if ([sourceValue isKindOfClass:[NSSet class]]) {
|
|
comparisonSelector = @selector(isEqualToSet:);
|
|
} else {
|
|
comparisonSelector = @selector(isEqual:);
|
|
}
|
|
|
|
// Comparison magic using function pointers. See this page for details: http://www.red-sweater.com/blog/320/abusing-objective-c-with-class
|
|
// Original code courtesy of Greg Parker
|
|
// This is necessary because isEqualToNumber will return negative integer values that aren't coercable directly to BOOL's without help [sbw]
|
|
BOOL (*ComparisonSender)(id, SEL, id) = (BOOL (*)(id, SEL, id)) objc_msgSend;
|
|
return ComparisonSender(sourceValue, comparisonSelector, destinationValue);
|
|
}
|
|
|
|
@implementation RKObjectMappingOperation
|
|
|
|
@synthesize sourceObject = _sourceObject;
|
|
@synthesize destinationObject = _destinationObject;
|
|
@synthesize objectMapping = _objectMapping;
|
|
@synthesize delegate = _delegate;
|
|
|
|
+ (RKObjectMappingOperation*)mappingOperationFromObject:(id)sourceObject toObject:(id)destinationObject withMapping:(id<RKObjectMappingDefinition>)objectMapping {
|
|
return [[[self alloc] initWithSourceObject:sourceObject destinationObject:destinationObject mapping:objectMapping] autorelease];
|
|
}
|
|
|
|
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(id<RKObjectMappingDefinition>)objectMapping {
|
|
NSAssert(sourceObject != nil, @"Cannot perform a mapping operation without a sourceObject object");
|
|
NSAssert(destinationObject != nil, @"Cannot perform a mapping operation without a destinationObject");
|
|
NSAssert(objectMapping != nil, @"Cannot perform a mapping operation without a mapping");
|
|
|
|
self = [super init];
|
|
if (self) {
|
|
_sourceObject = [sourceObject retain];
|
|
_destinationObject = [destinationObject retain];
|
|
|
|
if ([objectMapping isKindOfClass:[RKObjectDynamicMapping class]]) {
|
|
_objectMapping = [[(RKObjectDynamicMapping*)objectMapping objectMappingForDictionary:_sourceObject] retain];
|
|
RKLogDebug(@"RKObjectMappingOperation was initialized with a dynamic mapping. Determined concrete mapping = %@", _objectMapping);
|
|
} else if ([objectMapping isKindOfClass:[RKObjectMapping class]]) {
|
|
_objectMapping = (RKObjectMapping*)[objectMapping retain];
|
|
}
|
|
NSAssert(_objectMapping, @"Cannot perform a mapping operation with an object mapping");
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[_sourceObject release];
|
|
[_destinationObject release];
|
|
[_objectMapping release];
|
|
[_nestedAttributeSubstitution release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSDate*)parseDateFromString:(NSString*)string {
|
|
RKLogTrace(@"Transforming string value '%@' to NSDate...", string);
|
|
|
|
NSDate* date = nil;
|
|
for (NSDateFormatter *dateFormatter in self.objectMapping.dateFormatters) {
|
|
@synchronized(dateFormatter) {
|
|
date = [dateFormatter dateFromString:string];
|
|
}
|
|
if (date) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
- (id)transformValue:(id)value atKeyPath:keyPath toType:(Class)destinationType {
|
|
RKLogTrace(@"Found transformable value at keyPath '%@'. Transforming from type '%@' to '%@'", keyPath, NSStringFromClass([value class]), NSStringFromClass(destinationType));
|
|
Class sourceType = [value class];
|
|
|
|
if ([sourceType isSubclassOfClass:[NSString class]]) {
|
|
if ([destinationType isSubclassOfClass:[NSDate class]]) {
|
|
// String -> Date
|
|
return [self parseDateFromString:(NSString*)value];
|
|
} else if ([destinationType isSubclassOfClass:[NSURL class]]) {
|
|
// String -> URL
|
|
return [NSURL URLWithString:(NSString*)value];
|
|
} else if ([destinationType isSubclassOfClass:[NSDecimalNumber class]]) {
|
|
// String -> Decimal Number
|
|
return [NSDecimalNumber decimalNumberWithString:(NSString*)value];
|
|
} else if ([destinationType isSubclassOfClass:[NSNumber class]]) {
|
|
// String -> Number
|
|
NSString* lowercasedString = [(NSString*)value lowercaseString];
|
|
NSSet* trueStrings = [NSSet setWithObjects:@"true", @"t", @"yes", nil];
|
|
NSSet* booleanStrings = [trueStrings setByAddingObjectsFromSet:[NSSet setWithObjects:@"false", @"f", @"no", nil]];
|
|
if ([booleanStrings containsObject:lowercasedString]) {
|
|
// Handle booleans encoded as Strings
|
|
return [NSNumber numberWithBool:[trueStrings containsObject:lowercasedString]];
|
|
} else {
|
|
return [NSNumber numberWithDouble:[(NSString*)value doubleValue]];
|
|
}
|
|
}
|
|
} else if (value == [NSNull null] || [value isEqual:[NSNull null]]) {
|
|
// Transform NSNull -> nil for simplicity
|
|
return nil;
|
|
} else if ([sourceType isSubclassOfClass:[NSSet class]]) {
|
|
// Set -> Array
|
|
if ([destinationType isSubclassOfClass:[NSArray class]]) {
|
|
return [(NSSet*)value allObjects];
|
|
}
|
|
} else if ([sourceType isSubclassOfClass:[NSArray class]]) {
|
|
// Array -> Set
|
|
if ([destinationType isSubclassOfClass:[NSSet class]]) {
|
|
return [NSSet setWithArray:value];
|
|
}
|
|
} else if ([sourceType isSubclassOfClass:[NSNumber class]] && [destinationType isSubclassOfClass:[NSDate class]]) {
|
|
// Number -> Date
|
|
return [NSDate dateWithTimeIntervalSince1970:[(NSNumber*)value intValue]];
|
|
} else if ([sourceType isSubclassOfClass:[NSNumber class]] && [destinationType isSubclassOfClass:[NSDecimalNumber class]]) {
|
|
// Number -> Decimal Number
|
|
return [NSDecimalNumber decimalNumberWithDecimal:[value decimalValue]];
|
|
} else if ( ([sourceType isSubclassOfClass:NSClassFromString(@"__NSCFBoolean")] ||
|
|
[sourceType isSubclassOfClass:NSClassFromString(@"NSCFBoolean")] ) &&
|
|
[destinationType isSubclassOfClass:[NSString class]]) {
|
|
return ([value boolValue] ? @"true" : @"false");
|
|
} else if ([destinationType isSubclassOfClass:[NSString class]] && [value respondsToSelector:@selector(stringValue)]) {
|
|
return [value stringValue];
|
|
} else if ([destinationType isSubclassOfClass:[NSString class]] && [value isKindOfClass:[NSDate class]]) {
|
|
// NSDate -> NSString
|
|
// Transform using the preferred date formatter
|
|
NSString* dateString = nil;
|
|
@synchronized(self.objectMapping.preferredDateFormatter) {
|
|
dateString = [self.objectMapping.preferredDateFormatter stringFromDate:value];
|
|
}
|
|
return dateString;
|
|
}
|
|
|
|
RKLogWarning(@"Failed transformation of value at keyPath '%@'. No strategy for transforming from '%@' to '%@'", keyPath, NSStringFromClass([value class]), NSStringFromClass(destinationType));
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (BOOL)isValue:(id)sourceValue equalToValue:(id)destinationValue {
|
|
return RKObjectIsValueEqualToValue(sourceValue, destinationValue);
|
|
}
|
|
|
|
- (BOOL)validateValue:(id)value atKeyPath:(NSString*)keyPath {
|
|
BOOL success = YES;
|
|
|
|
if (self.objectMapping.performKeyValueValidation && [self.destinationObject respondsToSelector:@selector(validateValue:forKey:error:)]) {
|
|
success = [self.destinationObject validateValue:&value forKey:keyPath error:&_validationError];
|
|
if (!success) {
|
|
if (_validationError) {
|
|
RKLogError(@"Validation failed while mapping attribute at key path %@ to value %@. Error: %@", keyPath, value, [_validationError localizedDescription]);
|
|
} else {
|
|
RKLogWarning(@"Destination object %@ rejected attribute value %@ for keyPath %@. Skipping...", self.destinationObject, value, keyPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)shouldSetValue:(id)value atKeyPath:(NSString*)keyPath {
|
|
id currentValue = [self.destinationObject valueForKeyPath:keyPath];
|
|
if (currentValue == [NSNull null] || [currentValue isEqual:[NSNull null]]) {
|
|
currentValue = nil;
|
|
}
|
|
|
|
if (nil == currentValue && nil == value) {
|
|
// Both are nil
|
|
return NO;
|
|
} else if (nil == value || nil == currentValue) {
|
|
// One is nil and the other is not
|
|
return [self validateValue:value atKeyPath:keyPath];
|
|
}
|
|
|
|
if (! [self isValue:value equalToValue:currentValue]) {
|
|
// Validate value for key
|
|
return [self validateValue:value atKeyPath:keyPath];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSArray*)applyNestingToMappings:(NSArray*)mappings {
|
|
if (_nestedAttributeSubstitution) {
|
|
NSString* searchString = [NSString stringWithFormat:@"(%@)", [[_nestedAttributeSubstitution allKeys] lastObject]];
|
|
NSString* replacementString = [[_nestedAttributeSubstitution allValues] lastObject];
|
|
NSMutableArray* array = [NSMutableArray arrayWithCapacity:[self.objectMapping.attributeMappings count]];
|
|
for (RKObjectAttributeMapping* mapping in mappings) {
|
|
RKObjectAttributeMapping* nestedMapping = [mapping copy];
|
|
nestedMapping.sourceKeyPath = [nestedMapping.sourceKeyPath stringByReplacingOccurrencesOfString:searchString withString:replacementString];
|
|
nestedMapping.destinationKeyPath = [nestedMapping.destinationKeyPath stringByReplacingOccurrencesOfString:searchString withString:replacementString];
|
|
[array addObject:nestedMapping];
|
|
[nestedMapping release];
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
return mappings;
|
|
}
|
|
|
|
- (NSArray*)attributeMappings {
|
|
return [self applyNestingToMappings:self.objectMapping.attributeMappings];
|
|
}
|
|
|
|
- (NSArray*)relationshipMappings {
|
|
return [self applyNestingToMappings:self.objectMapping.relationshipMappings];
|
|
}
|
|
|
|
- (void)applyAttributeMapping:(RKObjectAttributeMapping*)attributeMapping withValue:(id)value {
|
|
if ([self.delegate respondsToSelector:@selector(objectMappingOperation:didFindMapping:forKeyPath:)]) {
|
|
[self.delegate objectMappingOperation:self didFindMapping:attributeMapping forKeyPath:attributeMapping.sourceKeyPath];
|
|
}
|
|
RKLogTrace(@"Mapping attribute value keyPath '%@' to '%@'", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath);
|
|
|
|
// Inspect the property type to handle any value transformations
|
|
Class type = [self.objectMapping classForProperty:attributeMapping.destinationKeyPath];
|
|
// Class type = [[RKObjectPropertyInspector sharedInspector] typeForProperty:attributeMapping.destinationKeyPath ofClass:[self.destinationObject class]];
|
|
if (type && NO == [[value class] isSubclassOfClass:type]) {
|
|
value = [self transformValue:value atKeyPath:attributeMapping.sourceKeyPath toType:type];
|
|
}
|
|
|
|
// Ensure that the value is different
|
|
if ([self shouldSetValue:value atKeyPath:attributeMapping.destinationKeyPath]) {
|
|
RKLogTrace(@"Mapped attribute value from keyPath '%@' to '%@'. Value: %@", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, value);
|
|
|
|
[self.destinationObject setValue:value forKey:attributeMapping.destinationKeyPath];
|
|
if ([self.delegate respondsToSelector:@selector(objectMappingOperation:didSetValue:forKeyPath:usingMapping:)]) {
|
|
[self.delegate objectMappingOperation:self didSetValue:value forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping];
|
|
}
|
|
} else {
|
|
RKLogTrace(@"Skipped mapping of attribute value from keyPath '%@ to keyPath '%@' -- value is unchanged (%@)", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, value);
|
|
}
|
|
}
|
|
|
|
// Return YES if we mapped any attributes
|
|
- (BOOL)applyAttributeMappings {
|
|
// If we have a nesting substitution value, we have alread
|
|
BOOL appliedMappings = (_nestedAttributeSubstitution != nil);
|
|
|
|
if (!self.objectMapping.performKeyValueValidation) {
|
|
RKLogDebug(@"Key-value validation is disabled for mapping, skipping...");
|
|
}
|
|
|
|
for (RKObjectAttributeMapping* attributeMapping in [self attributeMappings]) {
|
|
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
|
RKLogTrace(@"Skipping attribute mapping for special keyPath '%@'", RKObjectMappingNestingAttributeKeyName);
|
|
continue;
|
|
}
|
|
|
|
id value = nil;
|
|
if ([attributeMapping.sourceKeyPath isEqualToString:@""]) {
|
|
value = self.sourceObject;
|
|
} else {
|
|
value = [self.sourceObject valueForKeyPath:attributeMapping.sourceKeyPath];
|
|
}
|
|
if (value) {
|
|
appliedMappings = YES;
|
|
[self applyAttributeMapping:attributeMapping withValue:value];
|
|
} else {
|
|
if ([self.delegate respondsToSelector:@selector(objectMappingOperation:didNotFindMappingForKeyPath:)]) {
|
|
[self.delegate objectMappingOperation:self didNotFindMappingForKeyPath:attributeMapping.sourceKeyPath];
|
|
}
|
|
RKLogTrace(@"Did not find mappable attribute value keyPath '%@'", attributeMapping.sourceKeyPath);
|
|
|
|
// Optionally set the default value for missing values
|
|
if ([self.objectMapping shouldSetDefaultValueForMissingAttributes]) {
|
|
[self.destinationObject setValue:[self.objectMapping defaultValueForMissingAttribute:attributeMapping.destinationKeyPath]
|
|
forKey:attributeMapping.destinationKeyPath];
|
|
RKLogTrace(@"Setting nil for missing attribute value at keyPath '%@'", attributeMapping.sourceKeyPath);
|
|
}
|
|
}
|
|
|
|
// Fail out if an error has occurred
|
|
if (_validationError) {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return appliedMappings;
|
|
}
|
|
|
|
- (BOOL)isValueACollection:(id)value {
|
|
return ([value isKindOfClass:[NSSet class]] || [value isKindOfClass:[NSArray class]]);
|
|
}
|
|
|
|
- (BOOL)mapNestedObject:(id)anObject toObject:(id)anotherObject withRealtionshipMapping:(RKObjectRelationshipMapping*)relationshipMapping {
|
|
NSAssert(anObject, @"Cannot map nested object without a nested source object");
|
|
NSAssert(anotherObject, @"Cannot map nested object without a destination object");
|
|
NSAssert(relationshipMapping, @"Cannot map a nested object relationship without a relationship mapping");
|
|
NSError* error = nil;
|
|
|
|
RKObjectMappingOperation* subOperation = [RKObjectMappingOperation mappingOperationFromObject:anObject toObject:anotherObject withMapping:relationshipMapping.mapping];
|
|
subOperation.delegate = self.delegate;
|
|
if (NO == [subOperation performMapping:&error]) {
|
|
RKLogWarning(@"WARNING: Failed mapping nested object: %@", [error localizedDescription]);
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)applyRelationshipMappings {
|
|
BOOL appliedMappings = NO;
|
|
id destinationObject = nil;
|
|
|
|
for (RKObjectRelationshipMapping* relationshipMapping in [self relationshipMappings]) {
|
|
id value = [self.sourceObject valueForKeyPath:relationshipMapping.sourceKeyPath];
|
|
|
|
if (value == nil || value == [NSNull null] || [value isEqual:[NSNull null]]) {
|
|
RKLogDebug(@"Did not find mappable relationship value keyPath '%@'", relationshipMapping.sourceKeyPath);
|
|
|
|
// Optionally nil out the property
|
|
if ([self.objectMapping setNilForMissingRelationships] && [self shouldSetValue:nil atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
RKLogTrace(@"Setting nil for missing relationship value at keyPath '%@'", relationshipMapping.sourceKeyPath);
|
|
[self.destinationObject setValue:nil forKey:relationshipMapping.destinationKeyPath];
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Handle case where incoming content is a single object, but we want a collection
|
|
Class relationshipType = [self.objectMapping classForProperty:relationshipMapping.destinationKeyPath];
|
|
BOOL mappingToCollection = (relationshipType &&
|
|
([relationshipType isSubclassOfClass:[NSSet class]] || [relationshipType isSubclassOfClass:[NSArray class]]));
|
|
if (mappingToCollection && ![self isValueACollection:value]) {
|
|
RKLogDebug(@"Asked to map a single object into a collection relationship. Transforming to an instance of: %@", NSStringFromClass(relationshipType));
|
|
if ([relationshipType isSubclassOfClass:[NSArray class]]) {
|
|
value = [relationshipType arrayWithObject:value];
|
|
} else if ([relationshipType isSubclassOfClass:[NSSet class]]) {
|
|
value = [relationshipType setWithObject:value];
|
|
} else {
|
|
RKLogWarning(@"Failed to transform single object");
|
|
}
|
|
}
|
|
|
|
if ([self isValueACollection:value]) {
|
|
// One to many relationship
|
|
RKLogDebug(@"Mapping one to many relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath);
|
|
appliedMappings = YES;
|
|
|
|
destinationObject = [NSMutableArray arrayWithCapacity:[value count]];
|
|
for (id nestedObject in value) {
|
|
id<RKObjectMappingDefinition> mapping = relationshipMapping.mapping;
|
|
RKObjectMapping* objectMapping = nil;
|
|
if ([mapping isKindOfClass:[RKObjectDynamicMapping class]]) {
|
|
objectMapping = [(RKObjectDynamicMapping*)mapping objectMappingForDictionary:nestedObject];
|
|
if (! objectMapping) {
|
|
RKLogDebug(@"Mapping %@ declined mapping for data %@: returned nil objectMapping", mapping, nestedObject);
|
|
continue;
|
|
}
|
|
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
|
|
objectMapping = (RKObjectMapping*)mapping;
|
|
} else {
|
|
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([mapping class]));
|
|
}
|
|
id mappedObject = [objectMapping mappableObjectForData:nestedObject];
|
|
if ([self mapNestedObject:nestedObject toObject:mappedObject withRealtionshipMapping:relationshipMapping]) {
|
|
[destinationObject addObject:mappedObject];
|
|
}
|
|
}
|
|
|
|
// Transform from NSSet <-> NSArray if necessary
|
|
Class type = [self.objectMapping classForProperty:relationshipMapping.destinationKeyPath];
|
|
if (type && NO == [[destinationObject class] isSubclassOfClass:type]) {
|
|
destinationObject = [self transformValue:destinationObject atKeyPath:relationshipMapping.sourceKeyPath toType:type];
|
|
}
|
|
|
|
// If the relationship has changed, set it
|
|
if ([self shouldSetValue:destinationObject atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
Class managedObjectClass = NSClassFromString(@"NSManagedObject");
|
|
if (managedObjectClass && [self.destinationObject isKindOfClass:managedObjectClass]) {
|
|
RKLogTrace(@"Found a managedObject collection. About to apply value via mutable[Set|Array]ValueForKey");
|
|
if ([destinationObject isKindOfClass:[NSSet class]]) {
|
|
RKLogTrace(@"Mapped NSSet relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject);
|
|
NSMutableSet* destinationSet = [self.destinationObject mutableSetValueForKey:relationshipMapping.destinationKeyPath];
|
|
[destinationSet setSet:destinationObject];
|
|
} else if ([destinationObject isKindOfClass:[NSArray class]]) {
|
|
RKLogTrace(@"Mapped NSArray relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject);
|
|
NSMutableArray* destinationArray = [self.destinationObject mutableArrayValueForKey:relationshipMapping.destinationKeyPath];
|
|
[destinationArray setArray:destinationObject];
|
|
}
|
|
} else {
|
|
RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject);
|
|
[self.destinationObject setValue:destinationObject forKey:relationshipMapping.destinationKeyPath];
|
|
}
|
|
}
|
|
} else {
|
|
// One to one relationship
|
|
RKLogDebug(@"Mapping one to one relationship value at keyPath '%@' to '%@'", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath);
|
|
|
|
id<RKObjectMappingDefinition> mapping = relationshipMapping.mapping;
|
|
RKObjectMapping* objectMapping = nil;
|
|
if ([mapping isKindOfClass:[RKObjectDynamicMapping class]]) {
|
|
objectMapping = [(RKObjectDynamicMapping*)mapping objectMappingForDictionary:value];
|
|
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
|
|
objectMapping = (RKObjectMapping*)mapping;
|
|
}
|
|
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([mapping class]));
|
|
destinationObject = [objectMapping mappableObjectForData:value];
|
|
if ([self mapNestedObject:value toObject:destinationObject withRealtionshipMapping:relationshipMapping]) {
|
|
appliedMappings = YES;
|
|
}
|
|
}
|
|
|
|
// If the relationship has changed, set it
|
|
if ([self shouldSetValue:destinationObject atKeyPath:relationshipMapping.destinationKeyPath]) {
|
|
RKLogTrace(@"Mapped relationship object from keyPath '%@' to '%@'. Value: %@", relationshipMapping.sourceKeyPath, relationshipMapping.destinationKeyPath, destinationObject);
|
|
[self.destinationObject setValue:destinationObject forKey:relationshipMapping.destinationKeyPath];
|
|
}
|
|
|
|
// Fail out if a validation error has occurred
|
|
if (_validationError) {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
return appliedMappings;
|
|
}
|
|
|
|
- (void)applyNestedMappings {
|
|
RKObjectAttributeMapping* attributeMapping = [self.objectMapping mappingForKeyPath:RKObjectMappingNestingAttributeKeyName];
|
|
if (attributeMapping) {
|
|
RKLogDebug(@"Found nested mapping definition to attribute '%@'", attributeMapping.destinationKeyPath);
|
|
id attributeValue = [[self.sourceObject allKeys] lastObject];
|
|
if (attributeValue) {
|
|
RKLogDebug(@"Found nesting value of '%@' for attribute '%@'", attributeValue, attributeMapping.destinationKeyPath);
|
|
_nestedAttributeSubstitution = [[NSDictionary alloc] initWithObjectsAndKeys:attributeValue, attributeMapping.destinationKeyPath, nil];
|
|
[self applyAttributeMapping:attributeMapping withValue:attributeValue];
|
|
} else {
|
|
RKLogWarning(@"Unable to find nesting value for attribute '%@'", attributeMapping.destinationKeyPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)performMapping:(NSError**)error {
|
|
RKLogDebug(@"Starting mapping operation...");
|
|
RKLogTrace(@"Performing mapping operation: %@", self);
|
|
|
|
[self applyNestedMappings];
|
|
BOOL mappedAttributes = [self applyAttributeMappings];
|
|
BOOL mappedRelationships = [self applyRelationshipMappings];
|
|
if ((mappedAttributes || mappedRelationships) && _validationError == nil) {
|
|
RKLogDebug(@"Finished mapping operation successfully...");
|
|
return YES;
|
|
}
|
|
|
|
if (_validationError) {
|
|
// We failed out due to validation
|
|
if (error) *error = _validationError;
|
|
if ([self.delegate respondsToSelector:@selector(objectMappingOperation:didFailWithError:)]) {
|
|
[self.delegate objectMappingOperation:self didFailWithError:_validationError];
|
|
}
|
|
|
|
RKLogError(@"Failed mapping operation: %@", [_validationError localizedDescription]);
|
|
} else {
|
|
// We did not find anything to do
|
|
RKLogDebug(@"Mapping operation did not find any mappable content");
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (NSString*)description {
|
|
return [NSString stringWithFormat:@"RKObjectMappingOperation for '%@' object. Mapping values from object %@ to object %@ with object mapping %@",
|
|
NSStringFromClass([self.destinationObject class]), self.sourceObject, self.destinationObject, self.objectMapping];
|
|
}
|
|
|
|
@end
|