mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-25 21:35:36 +08:00
Fixed issue with order dependence in Core Data connections. fixes #173
Since OM 2.0 connection of relationships happened during the object mapping operation instead of aggregately at the end of the process. In this commit, we have introduced a lightweight queue for deferring portions of the mapping operation until a larger aggregate mapping has completed. The changes are as follows: * Introduced RKMappingOperationQueue for queueing portions of mapping. This is a synchronous queue modeled off of NSOperationQueue that does NOT use threading (for Core Data friendliness). * RKObjectMappingOperation now has a RKMappingOperationQueue queue property that defaults to nil * RKObjectMappingOperation instances built via RKObjectMapper will has a mapping operation queue assigned to the property. * If a queue is present, RKManagedObjectMappingOperation will use it to defer the connection of relationships. * At the end of an RKObjectMapper process, the mapping operation queue used by all mapping operations created during the process will be executed. This allows all relationships to be connected after all object creation has completed. The queue is general purpose, though currently only used for the connection of relationships.
This commit is contained in:
@@ -23,6 +23,10 @@
|
||||
#import "NSManagedObject+ActiveRecord.h"
|
||||
#import "../Support/RKLog.h"
|
||||
|
||||
// Set Logging Component
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent lcl_cRestKitCoreData
|
||||
|
||||
/**
|
||||
Progressively enhance the RKObjectMappingOperation base class to inject Core Data
|
||||
specifics without leaking into the object mapper abstractions
|
||||
@@ -56,28 +60,48 @@
|
||||
return [RKObjectMappingOperation class];
|
||||
}
|
||||
|
||||
- (void)connectRelationship:(NSString *)relationshipName {
|
||||
NSDictionary* relationshipsAndPrimaryKeyAttributes = [(RKManagedObjectMapping*)self.objectMapping relationshipsAndPrimaryKeyAttributes];
|
||||
NSString* primaryKeyAttribute = [relationshipsAndPrimaryKeyAttributes objectForKey:relationshipName];
|
||||
RKObjectRelationshipMapping* relationshipMapping = [self.objectMapping mappingForKeyPath:relationshipName];
|
||||
id<RKObjectMappingDefinition> mapping = relationshipMapping.mapping;
|
||||
NSAssert(mapping, @"Attempted to connect relationship for keyPath '%@' without a relationship mapping defined.");
|
||||
if (! [mapping isKindOfClass:[RKObjectMapping class]]) {
|
||||
RKLogWarning(@"Can only connect relationships for RKObjectMapping relationships. Found %@: Skipping...", NSStringFromClass([mapping class]));
|
||||
return;
|
||||
}
|
||||
RKObjectMapping* objectMapping = (RKObjectMapping*)mapping;
|
||||
NSAssert(relationshipMapping, @"Unable to find relationship mapping '%@' to connect by primaryKey", relationshipName);
|
||||
NSAssert([relationshipMapping isKindOfClass:[RKObjectRelationshipMapping class]], @"Expected mapping for %@ to be a relationship mapping", relationshipName);
|
||||
NSAssert([relationshipMapping.mapping isKindOfClass:[RKManagedObjectMapping class]], @"Can only connect RKManagedObjectMapping relationships");
|
||||
NSString* primaryKeyAttributeOfRelatedObject = [(RKManagedObjectMapping*)objectMapping primaryKeyAttribute];
|
||||
NSAssert(primaryKeyAttributeOfRelatedObject, @"Cannot connect relationship: mapping for %@ has no primary key attribute specified", NSStringFromClass(objectMapping.objectClass));
|
||||
id valueOfLocalPrimaryKeyAttribute = [self.destinationObject valueForKey:primaryKeyAttribute];
|
||||
RKLogDebug(@"Connecting relationship at keyPath '%@' to object with primaryKey attribute '%@'", relationshipName, primaryKeyAttributeOfRelatedObject);
|
||||
if (valueOfLocalPrimaryKeyAttribute) {
|
||||
id relatedObject = [objectMapping.objectClass findFirstByAttribute:primaryKeyAttributeOfRelatedObject withValue:valueOfLocalPrimaryKeyAttribute];
|
||||
if (relatedObject) {
|
||||
RKLogTrace(@"Connected relationship '%@' to object with primary key value '%@': %@", relationshipName, valueOfLocalPrimaryKeyAttribute, relatedObject);
|
||||
} else {
|
||||
RKLogTrace(@"Failed to find object to connect relationship '%@' with primary key value '%@'", relationshipName, valueOfLocalPrimaryKeyAttribute);
|
||||
}
|
||||
[self.destinationObject setValue:relatedObject forKey:relationshipName];
|
||||
} else {
|
||||
RKLogTrace(@"Failed to find primary key value for attribute '%@'", primaryKeyAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectRelationships {
|
||||
if ([self.objectMapping isKindOfClass:[RKManagedObjectMapping class]]) {
|
||||
NSDictionary* relationshipsAndPrimaryKeyAttributes = [(RKManagedObjectMapping*)self.objectMapping relationshipsAndPrimaryKeyAttributes];
|
||||
for (NSString* relationshipName in relationshipsAndPrimaryKeyAttributes) {
|
||||
NSString* primaryKeyAttribute = [relationshipsAndPrimaryKeyAttributes objectForKey:relationshipName];
|
||||
RKObjectRelationshipMapping* relationshipMapping = [self.objectMapping mappingForKeyPath:relationshipName];
|
||||
id<RKObjectMappingDefinition> mapping = relationshipMapping.mapping;
|
||||
if (! [mapping isKindOfClass:[RKObjectMapping class]]) {
|
||||
RKLogWarning(@"Can only connect relationships for RKObjectMapping relationships. Found %@: Skipping...", NSStringFromClass([mapping class]));
|
||||
continue;
|
||||
}
|
||||
RKObjectMapping* objectMapping = (RKObjectMapping*)mapping;
|
||||
NSAssert(relationshipMapping, @"Unable to find relationship mapping '%@' to connect by primaryKey", relationshipName);
|
||||
NSAssert([relationshipMapping isKindOfClass:[RKObjectRelationshipMapping class]], @"Expected mapping for %@ to be a relationship mapping", relationshipName);
|
||||
NSAssert([relationshipMapping.mapping isKindOfClass:[RKManagedObjectMapping class]], @"Can only connect RKManagedObjectMapping relationships");
|
||||
NSString* primaryKeyAttributeOfRelatedObject = [(RKManagedObjectMapping*)objectMapping primaryKeyAttribute];
|
||||
NSAssert(primaryKeyAttributeOfRelatedObject, @"Cannot connect relationship: mapping for %@ has no primary key attribute specified", NSStringFromClass(objectMapping.objectClass));
|
||||
id valueOfLocalPrimaryKeyAttribute = [self.destinationObject valueForKey:primaryKeyAttribute];
|
||||
if (valueOfLocalPrimaryKeyAttribute) {
|
||||
id relatedObject = [objectMapping.objectClass findFirstByAttribute:primaryKeyAttributeOfRelatedObject withValue:valueOfLocalPrimaryKeyAttribute];
|
||||
[self.destinationObject setValue:relatedObject forKey:relationshipName];
|
||||
// TODO: Logging
|
||||
if (self.queue) {
|
||||
RKLogTrace(@"Enqueueing relationship connection using operation queue");
|
||||
[self.queue addOperationWithBlock:^{
|
||||
[self connectRelationship:relationshipName];
|
||||
}];
|
||||
} else {
|
||||
[self connectRelationship:relationshipName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
64
Code/ObjectMapping/RKMappingOperationQueue.h
Normal file
64
Code/ObjectMapping/RKMappingOperationQueue.h
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// RKMappingOperationQueue.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 9/20/11.
|
||||
// Copyright (c) 2011 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Provides a simple interface for deferring portion of an larger object mapping
|
||||
operation until the entire aggregate operation has completed. This is used by Core
|
||||
Data to connect all object relationships once the entire object graph has been mapped,
|
||||
rather than as each object is encountered.
|
||||
|
||||
Designed as a lightweight workalike for NSOperationQueue, which was not usable do to
|
||||
its reliance on threading for concurrent operations. The threading was causing problems
|
||||
with managed objects due to MOC being thread specific.
|
||||
|
||||
This class is not intended to be thread-safe and is used for queueing non-concurrent
|
||||
operations that will be executed within the object mapper only. It is not a general purpose
|
||||
work queue.
|
||||
*/
|
||||
@interface RKMappingOperationQueue : NSObject {
|
||||
@protected
|
||||
NSMutableArray *_operations;
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an NSOperation to the queue for later execution
|
||||
|
||||
@param op The operation to enqueue
|
||||
*/
|
||||
- (void)addOperation:(NSOperation *)op;
|
||||
|
||||
/**
|
||||
Adds an NSBlockOperation to the queue configured to executed the block passed
|
||||
|
||||
@param block A block to wrap into an operation for later execution
|
||||
*/
|
||||
- (void)addOperationWithBlock:(void (^)(void))block;
|
||||
|
||||
/**
|
||||
Returns the collection of operations in the queue
|
||||
|
||||
@return A new aray containing the NSOperation objects in the order in which they were added to the queue
|
||||
*/
|
||||
- (NSArray *)operations;
|
||||
|
||||
/**
|
||||
Returns the number of operations in the queue
|
||||
|
||||
@return The number of operations in the queue.
|
||||
*/
|
||||
- (NSUInteger)operationCount;
|
||||
|
||||
/**
|
||||
Starts the execution of all operations in the queue in the order in which they were added to the queue. The
|
||||
current threads execution will be blocked until all enqueued operations have returned.
|
||||
*/
|
||||
- (void)waitUntilAllOperationsAreFinished;
|
||||
|
||||
@end
|
||||
50
Code/ObjectMapping/RKMappingOperationQueue.m
Normal file
50
Code/ObjectMapping/RKMappingOperationQueue.m
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// RKMappingOperationQueue.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 9/20/11.
|
||||
// Copyright (c) 2011 RestKit. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKMappingOperationQueue.h"
|
||||
|
||||
@implementation RKMappingOperationQueue
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_operations = [NSMutableArray new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_operations release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)addOperation:(NSOperation *)op {
|
||||
[_operations addObject:op];
|
||||
}
|
||||
|
||||
- (void)addOperationWithBlock:(void (^)(void))block {
|
||||
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block];
|
||||
[_operations addObject:blockOperation];
|
||||
}
|
||||
|
||||
- (NSArray *)operations {
|
||||
return [NSArray arrayWithArray:_operations];
|
||||
}
|
||||
|
||||
- (NSUInteger)operationCount {
|
||||
return [_operations count];
|
||||
}
|
||||
|
||||
- (void)waitUntilAllOperationsAreFinished {
|
||||
for (NSOperation *operation in _operations) {
|
||||
[operation start];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -23,6 +23,7 @@
|
||||
#import "RKObjectMappingOperation.h"
|
||||
#import "RKObjectMappingResult.h"
|
||||
#import "RKObjectMappingProvider.h"
|
||||
#import "RKMappingOperationQueue.h"
|
||||
#import "../Support/Support.h"
|
||||
|
||||
/**
|
||||
@@ -52,6 +53,7 @@
|
||||
RKObjectMappingProvider* _mappingProvider;
|
||||
id<RKObjectMapperDelegate> _delegate;
|
||||
NSMutableArray* _errors;
|
||||
RKMappingOperationQueue *_operationQueue;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) id sourceObject;
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
_sourceObject = [object retain];
|
||||
_mappingProvider = mappingProvider;
|
||||
_errors = [NSMutableArray new];
|
||||
_operationQueue = [RKMappingOperationQueue new];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -52,6 +53,7 @@
|
||||
- (void)dealloc {
|
||||
[_sourceObject release];
|
||||
[_errors release];
|
||||
[_operationQueue release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -207,7 +209,8 @@
|
||||
|
||||
RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:mappableObject
|
||||
toObject:destinationObject
|
||||
withMapping:mapping];
|
||||
withMapping:mapping];
|
||||
operation.queue = _operationQueue;
|
||||
BOOL success = [operation performMapping:&error];
|
||||
if (success) {
|
||||
if ([self.delegate respondsToSelector:@selector(objectMapper:didMapFromObject:toObject:atKeyPath:usingMapping:)]) {
|
||||
@@ -301,6 +304,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Allow any queued operations to complete
|
||||
NSLog(@"The following operations are in the queue: %@", _operationQueue.operations);
|
||||
[_operationQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(objectMapperDidFinishMapping:)]) {
|
||||
[self.delegate objectMapperDidFinishMapping:self];
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#import "RKObjectAttributeMapping.h"
|
||||
|
||||
@class RKObjectMappingOperation;
|
||||
@class RKMappingOperationQueue;
|
||||
|
||||
@protocol RKObjectMappingOperationDelegate <NSObject>
|
||||
|
||||
@@ -44,6 +45,7 @@
|
||||
id<RKObjectMappingOperationDelegate> _delegate;
|
||||
NSDictionary* _nestedAttributeSubstitution;
|
||||
NSError* _validationError;
|
||||
RKMappingOperationQueue *_queue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,6 +69,16 @@
|
||||
*/
|
||||
@property (nonatomic, assign) id<RKObjectMappingOperationDelegate> delegate;
|
||||
|
||||
/**
|
||||
An operation queue for deferring portions of the mapping process until later
|
||||
|
||||
Defaults to nil. If this mapping operation was configured by an instance of RKObjectMapper, then
|
||||
an instance of the operation queue will be configured and assigned for use. If the queue is nil,
|
||||
the mapping operation will perform all its operations within the body of performMapping. If a queue
|
||||
is present, it may elect to defer portions of the mapping operation using the queue.
|
||||
*/
|
||||
@property (nonatomic, retain) RKMappingOperationQueue *queue;
|
||||
|
||||
/**
|
||||
Create a new mapping operation configured to transform the object representation
|
||||
in a source object to a new destination object according to an object mapping definition
|
||||
|
||||
@@ -69,6 +69,7 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue) {
|
||||
@synthesize destinationObject = _destinationObject;
|
||||
@synthesize objectMapping = _objectMapping;
|
||||
@synthesize delegate = _delegate;
|
||||
@synthesize queue = _queue;
|
||||
|
||||
+ (RKObjectMappingOperation*)mappingOperationFromObject:(id)sourceObject toObject:(id)destinationObject withMapping:(id<RKObjectMappingDefinition>)objectMapping {
|
||||
return [[[self alloc] initWithSourceObject:sourceObject destinationObject:destinationObject mapping:objectMapping] autorelease];
|
||||
@@ -264,7 +265,6 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue) {
|
||||
|
||||
// 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];
|
||||
}
|
||||
@@ -341,6 +341,7 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue) {
|
||||
|
||||
RKObjectMappingOperation* subOperation = [RKObjectMappingOperation mappingOperationFromObject:anObject toObject:anotherObject withMapping:relationshipMapping.mapping];
|
||||
subOperation.delegate = self.delegate;
|
||||
subOperation.queue = self.queue;
|
||||
if (NO == [subOperation performMapping:&error]) {
|
||||
RKLogWarning(@"WARNING: Failed mapping nested object: %@", [error localizedDescription]);
|
||||
}
|
||||
|
||||
@@ -682,6 +682,7 @@
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "App/RKCatalog-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = "\"$(SOURCE_ROOT)/../../Build\"";
|
||||
INFOPLIST_FILE = "App/RKCatalog-Info.plist";
|
||||
LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)\"";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
@@ -697,6 +698,7 @@
|
||||
COPY_PHASE_STRIP = YES;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "App/RKCatalog-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = "\"$(SOURCE_ROOT)/../../Build\"";
|
||||
INFOPLIST_FILE = "App/RKCatalog-Info.plist";
|
||||
LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)\"";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
251939ED13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json in Resources */ = {isa = PBXBuildFile; fileRef = 251939EC13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json */; };
|
||||
251D14AC133597B800959061 /* RKManagedObjectLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 251D14AA133597B800959061 /* RKManagedObjectLoader.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
251D14AD133597B800959061 /* RKManagedObjectLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 251D14AB133597B800959061 /* RKManagedObjectLoader.m */; };
|
||||
2521ACB314282D6D008A9BA4 /* ConnectingParents.json in Resources */ = {isa = PBXBuildFile; fileRef = 2521ACB214282D6D008A9BA4 /* ConnectingParents.json */; };
|
||||
2521ACBB1428DF44008A9BA4 /* RKMappingOperationQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 2521ACB91428DF40008A9BA4 /* RKMappingOperationQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
2521ACBC1428DF44008A9BA4 /* RKMappingOperationQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2521ACBA1428DF42008A9BA4 /* RKMappingOperationQueue.m */; };
|
||||
2523363E11E7A1F00048F9B4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F6C3A9510FE7524008F47C5 /* UIKit.framework */; };
|
||||
252CF8B613E254C60093BBD6 /* RKObjectDynamicMappingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 252CF8B513E254C60093BBD6 /* RKObjectDynamicMappingSpec.m */; };
|
||||
252CF8B913E255D70093BBD6 /* boy.json in Resources */ = {isa = PBXBuildFile; fileRef = 252CF8B813E255D70093BBD6 /* boy.json */; };
|
||||
@@ -470,6 +473,9 @@
|
||||
251939EC13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = DynamicKeysWithNestedRelationship.json; sourceTree = "<group>"; };
|
||||
251D14AA133597B800959061 /* RKManagedObjectLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKManagedObjectLoader.h; sourceTree = "<group>"; };
|
||||
251D14AB133597B800959061 /* RKManagedObjectLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKManagedObjectLoader.m; sourceTree = "<group>"; };
|
||||
2521ACB214282D6D008A9BA4 /* ConnectingParents.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ConnectingParents.json; sourceTree = "<group>"; };
|
||||
2521ACB91428DF40008A9BA4 /* RKMappingOperationQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKMappingOperationQueue.h; sourceTree = "<group>"; };
|
||||
2521ACBA1428DF42008A9BA4 /* RKMappingOperationQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKMappingOperationQueue.m; sourceTree = "<group>"; };
|
||||
2523360511E79F090048F9B4 /* libRestKitThree20.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitThree20.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
252CF8B013E250730093BBD6 /* RKObjectDynamicMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKObjectDynamicMapping.h; sourceTree = "<group>"; };
|
||||
252CF8B113E250730093BBD6 /* RKObjectDynamicMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKObjectDynamicMapping.m; sourceTree = "<group>"; };
|
||||
@@ -1123,6 +1129,8 @@
|
||||
252CF8B013E250730093BBD6 /* RKObjectDynamicMapping.h */,
|
||||
252CF8B113E250730093BBD6 /* RKObjectDynamicMapping.m */,
|
||||
25E9682D13E6156100ABAE92 /* RKObjectMappingDefinition.h */,
|
||||
2521ACB91428DF40008A9BA4 /* RKMappingOperationQueue.h */,
|
||||
2521ACBA1428DF42008A9BA4 /* RKMappingOperationQueue.m */,
|
||||
);
|
||||
path = ObjectMapping;
|
||||
sourceTree = "<group>";
|
||||
@@ -1419,6 +1427,7 @@
|
||||
25952EDC136F563E00D04F93 /* humans */,
|
||||
25952EDF136F563E00D04F93 /* RailsUser.json */,
|
||||
25952EE0136F563E00D04F93 /* SameKeyDifferentTargetClasses.json */,
|
||||
2521ACB214282D6D008A9BA4 /* ConnectingParents.json */,
|
||||
);
|
||||
path = JSON;
|
||||
sourceTree = "<group>";
|
||||
@@ -1656,6 +1665,7 @@
|
||||
25FB6D5613E4836C00F48969 /* RKObjectDynamicMapping.h in Headers */,
|
||||
257FB6FF1395D36D003A628E /* RKObjectMapperError.h in Headers */,
|
||||
25E9682E13E6156100ABAE92 /* RKObjectMappingDefinition.h in Headers */,
|
||||
2521ACBB1428DF44008A9BA4 /* RKMappingOperationQueue.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2055,6 +2065,7 @@
|
||||
25769F191409CC9C003FCDBC /* orders.xml in Resources */,
|
||||
2537ABF31412A3B90043BE9F /* zend.xml in Resources */,
|
||||
2537ACFC1412E0380043BE9F /* national_weather_service.xml in Resources */,
|
||||
2521ACB314282D6D008A9BA4 /* ConnectingParents.json in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2183,6 +2194,7 @@
|
||||
257D2D7113759D70008E9649 /* RKObjectMappingResult.m in Sources */,
|
||||
253E04C613798D72005D2E15 /* RKErrorMessage.m in Sources */,
|
||||
25A1CB4413840C5400A7D5C9 /* RKParserRegistry.m in Sources */,
|
||||
2521ACBC1428DF44008A9BA4 /* RKMappingOperationQueue.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -146,4 +146,32 @@
|
||||
assertThatInt([[[parent.children anyObject] parents] count], is(equalToInt(1)));
|
||||
}
|
||||
|
||||
- (void)itShouldConnectRelationshipsByPrimaryKeyRegardlessOfOrder {
|
||||
RKSpecNewManagedObjectStore();
|
||||
RKManagedObjectMapping* parentMapping = [RKManagedObjectMapping mappingForClass:[RKParent class]];
|
||||
[parentMapping mapAttributes:@"parentID", nil];
|
||||
parentMapping.primaryKeyAttribute = @"parentID";
|
||||
|
||||
RKManagedObjectMapping* childMapping = [RKManagedObjectMapping mappingForClass:[RKChild class]];
|
||||
[childMapping mapAttributes:@"fatherID", nil];
|
||||
[childMapping mapRelationship:@"father" withMapping:parentMapping];
|
||||
[childMapping connectRelationship:@"father" withObjectForPrimaryKeyAttribute:@"fatherID"];
|
||||
|
||||
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new];
|
||||
// NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary
|
||||
// keys are not guaranteed to return in any particular order
|
||||
[mappingProvider setMapping:parentMapping forKeyPath:@"parents"];
|
||||
[mappingProvider setMapping:childMapping forKeyPath:@"children"];
|
||||
|
||||
NSDictionary *JSON = RKSpecParseFixture(@"ConnectingParents.json");
|
||||
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
|
||||
RKLogConfigureByName("RestKit/CoreData", RKLogLevelTrace);
|
||||
RKObjectMapper *mapper = [RKObjectMapper mapperWithObject:JSON mappingProvider:mappingProvider];
|
||||
RKObjectMappingResult *result = [mapper performMapping];
|
||||
NSArray *children = [[result asDictionary] valueForKey:@"children"];
|
||||
assertThat(children, hasCountOf(1));
|
||||
RKChild *child = [children lastObject];
|
||||
assertThat(child.father, is(notNilValue()));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
12
Specs/Fixtures/JSON/ConnectingParents.json
Normal file
12
Specs/Fixtures/JSON/ConnectingParents.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"fatherID": 1
|
||||
}
|
||||
],
|
||||
"parents": [
|
||||
{
|
||||
"parentID": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -27,6 +27,10 @@
|
||||
@interface RKChild : RKHuman {
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSSet* parents;
|
||||
@property (nonatomic, retain) NSSet *parents;
|
||||
@property (nonatomic, retain) NSNumber *motherID;
|
||||
@property (nonatomic, retain) NSNumber *fatherID;
|
||||
@property (nonatomic, retain) RKParent *father;
|
||||
@property (nonatomic, retain) RKParent *mother;
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,5 +24,9 @@
|
||||
@implementation RKChild
|
||||
|
||||
@dynamic parents;
|
||||
@dynamic motherID;
|
||||
@dynamic fatherID;
|
||||
@dynamic father;
|
||||
@dynamic mother;
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
@interface RKParent : RKHuman {
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSSet* children;
|
||||
@property (nonatomic, retain) NSSet *children;
|
||||
@property (nonatomic, retain) NSNumber *parentID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
@implementation RKParent
|
||||
|
||||
@dynamic children;
|
||||
@dynamic parentID;
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user