From f0e8bbe284f146199df3bdceb756f6e29a136d5a Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Mon, 21 May 2012 01:36:01 -0600 Subject: [PATCH] Update new connectRelationship implementation based on feedback from Blake. --- Code/CoreData/RKManagedObjectMapping.h | 20 +- Code/CoreData/RKManagedObjectMapping.m | 58 ++-- .../RKManagedObjectMappingOperation.m | 27 +- .../ObjectMapping/RKObjectConnectionMapping.h | 39 ++- .../ObjectMapping/RKObjectConnectionMapping.m | 30 +- .../RKManagedObjectMappingOperationTest.m | 299 ++++++++++++++++-- Tests/Models/RKCat.h | 1 + Tests/Models/RKCat.m | 1 + Tests/Models/RKHuman.h | 1 + Tests/Models/RKHuman.m | 1 + 10 files changed, 391 insertions(+), 86 deletions(-) diff --git a/Code/CoreData/RKManagedObjectMapping.h b/Code/CoreData/RKManagedObjectMapping.h index 5dfdb689..7ab334ba 100644 --- a/Code/CoreData/RKManagedObjectMapping.h +++ b/Code/CoreData/RKManagedObjectMapping.h @@ -29,7 +29,7 @@ entity. */ @interface RKManagedObjectMapping : RKObjectMapping { - NSMutableDictionary *_connections; + NSMutableArray *_connections; } /** @@ -74,7 +74,7 @@ /** Returns a dictionary containing Core Data connections */ -@property (nonatomic, readonly) NSDictionary *connections; +@property (nonatomic, readonly) NSArray *connections; /** The RKManagedObjectStore containing the Core Data entity being mapped @@ -87,8 +87,16 @@ - (RKObjectConnectionMapping*)mappingForConnection:(NSString*)relationshipName; /** - Instructs RestKit to automatically connect a relationship of the object being mapped by looking up - the related object by primary key. + Instructs RestKit to connect a relationship of the object being mapped to the + appropriate target object(s). It does this by using the value of the object's + fromKeyPath attribute to query instances of the target entity that have the + same value in their toKeyPath attribute. + + Note that connectRelationship runs *after* an object's attributes have been + mapped and is dependent upon the results of those mappings. Also, connectRelationship + will never create a new object - it simply looks up existing objects. In effect, + connectRelationship allows foreign key relationships between managed objects + to be automatically maintained from the server to the underlying Core Data object graph. For example, given a Project object associated with a User, where the 'user' relationship is specified by a userID property on the managed object: @@ -109,8 +117,6 @@ */ - (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath; - - /** Conditionally connect a relationship of the object being mapped when the object being mapped has keyPath equal to a specified value. @@ -129,7 +135,6 @@ */ - (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath whenValueOfKeyPath:(NSString *)keyPath isEqualTo:(id)value; - /** Conditionally connect a relationship of the object being mapped when the object being mapped has block evaluate to YES. This variant is useful in cases where you want to execute an arbitrary @@ -151,7 +156,6 @@ */ - (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath usingEvaluationBlock:(BOOL (^)(id data))block; - /** Initialize a managed object mapping with a Core Data entity description and a RestKit managed object store */ diff --git a/Code/CoreData/RKManagedObjectMapping.m b/Code/CoreData/RKManagedObjectMapping.m index 03d24864..2f6b31da 100644 --- a/Code/CoreData/RKManagedObjectMapping.m +++ b/Code/CoreData/RKManagedObjectMapping.m @@ -86,7 +86,7 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue); { self = [super init]; if (self) { - _connections = [[NSMutableDictionary alloc] init]; + _connections = [[NSMutableArray alloc] init]; } return self; @@ -102,56 +102,63 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue); [super dealloc]; } -- (NSDictionary *)connections +- (NSArray*)connections { return _connections; } -- (RKObjectRelationshipMapping *)mappingForConnection:(NSString *)relationshipName +- (RKObjectConnectionMapping *)mappingForConnection:(NSString *)relationshipName { - return [_connections objectForKey:relationshipName]; + for (RKObjectConnectionMapping *connection in self.connections) { + if ([connection.relationshipName isEqualToString:relationshipName]) { + return connection; + } + } + return nil; } -- (RKObjectMappingDefinition *)figureMapping:(NSString *)relationshipName { +- (RKObjectMappingDefinition *)objectMappingForRelationship:(NSString *)relationshipName +{ RKObjectRelationshipMapping *relationshipMapping = [self mappingForRelationship:relationshipName]; return relationshipMapping.mapping; } -- (NSString *)figurePrimaryKeyPath:(NSString *)relationshipName +- (NSString *)primaryKeyPathForRelationship:(NSString *)relationshipName { - RKObjectMappingDefinition* mappingDef = [self figureMapping:relationshipName]; + RKObjectMappingDefinition* mappingDef = [self objectMappingForRelationship:relationshipName]; RKManagedObjectMapping *objectMapping = (RKManagedObjectMapping *) mappingDef; return [objectMapping primaryKeyAttribute]; } -- (void)addConnectionMapping:(NSString *)relationshipName withMapping:(RKObjectConnectionMapping *)mapping +- (void)addConnectionMapping:(RKObjectConnectionMapping *)mapping { - NSAssert([_connections objectForKey:relationshipName] == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", relationshipName); + RKObjectConnectionMapping *connectionMapping = [self mappingForConnection:mapping.relationshipName]; + NSAssert(connectionMapping == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", mapping.relationshipName); NSAssert(mapping.mapping, @"Attempted to connect relationship for keyPath '%@' without a relationship mapping defined."); NSAssert([mapping.mapping isKindOfClass:[RKManagedObjectMapping class]], @"Can only connect RKManagedObjectMapping relationships"); - [_connections setObject:mapping forKey:relationshipName]; + [_connections addObject:mapping]; } - (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath { NSAssert(sourceKeyPath, @"Cannot connect relationship: mapping for %@ has no source key attribute specified", relationshipName); NSAssert(destinationKeyPath, @"Cannot connect relationship: mapping for %@ has no destination key attribute specified", relationshipName); - RKObjectConnectionMapping *mapping = [RKObjectConnectionMapping mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping]; - [self addConnectionMapping:relationshipName withMapping:mapping]; + RKObjectConnectionMapping *mapping = [RKObjectConnectionMapping mapping:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping]; + [self addConnectionMapping:mapping]; } - (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath whenValueOfKeyPath:(NSString *)keyPath isEqualTo:(id)value { - RKDynamicObjectMappingMatcher *matcher = [[RKDynamicObjectMappingMatcher alloc] initWithKey:keyPath value:value primaryKeyAttribute:sourceKeyPath]; - RKObjectConnectionMapping *mapping = [RKObjectConnectionMapping mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:matcher withMapping:objectOrDynamicMapping]; - [self addConnectionMapping:relationshipName withMapping:mapping]; + RKDynamicObjectMappingMatcher* matcher = [[RKDynamicObjectMappingMatcher alloc] initWithKey:keyPath value:value primaryKeyAttribute:sourceKeyPath]; + RKObjectConnectionMapping* mapping = [RKObjectConnectionMapping mapping:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:matcher withMapping:objectOrDynamicMapping]; + [self addConnectionMapping:mapping]; } - (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath usingEvaluationBlock:(BOOL (^)(id data))block { RKDynamicObjectMappingMatcher *matcher = [[RKDynamicObjectMappingMatcher alloc] initWithPrimaryKeyAttribute:sourceKeyPath evaluationBlock:block]; - RKObjectConnectionMapping *mapping = [RKObjectConnectionMapping mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:matcher withMapping:objectOrDynamicMapping]; - [self addConnectionMapping:relationshipName withMapping:mapping]; + RKObjectConnectionMapping *mapping = [RKObjectConnectionMapping mapping:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:matcher withMapping:objectOrDynamicMapping]; + [self addConnectionMapping:mapping]; } - (id)defaultValueForMissingAttribute:(NSString *)attributeName @@ -252,12 +259,12 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue); /* Deprecated */ - (void)connectRelationship:(NSString*)relationshipName withObjectForPrimaryKeyAttribute:(NSString*)primaryKeyAttribute { - RKObjectMappingDefinition *objectOrDynamicMapping = [self figureMapping:relationshipName]; + RKObjectMappingDefinition *objectOrDynamicMapping = [self objectMappingForRelationship:relationshipName]; NSString *sourceKeyPath = primaryKeyAttribute; - NSString *destinationKeyPath = [self figurePrimaryKeyPath:relationshipName]; + NSString *destinationKeyPath = [self primaryKeyPathForRelationship:relationshipName]; - RKObjectConnectionMapping* mapping = [RKObjectConnectionMapping mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping]; - [self addConnectionMapping:relationshipName withMapping:mapping]; + RKObjectConnectionMapping* mapping = [RKObjectConnectionMapping mapping:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping]; + [self addConnectionMapping:mapping]; } - (void)connectRelationshipsWithObjectsForPrimaryKeyAttributes:(NSString*)firstRelationshipName, ... @@ -274,18 +281,17 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue); - (void)connectRelationship:(NSString*)relationshipName withObjectForPrimaryKeyAttribute:(NSString*)primaryKeyAttribute whenValueOfKeyPath:(NSString*)keyPath isEqualTo:(id)value { - RKObjectMappingDefinition *objectOrDynamicMapping = [self figureMapping:relationshipName]; + RKObjectMappingDefinition *objectOrDynamicMapping = [self objectMappingForRelationship:relationshipName]; NSString *sourceKeyPath = primaryKeyAttribute; - NSString *destinationKeyPath = [self figurePrimaryKeyPath:relationshipName]; + NSString *destinationKeyPath = [self primaryKeyPathForRelationship:relationshipName]; [self connectRelationship:relationshipName withMapping:objectOrDynamicMapping fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath whenValueOfKeyPath:keyPath isEqualTo:value]; } - (void)connectRelationship:(NSString*)relationshipName withObjectForPrimaryKeyAttribute:(NSString*)primaryKeyAttribute usingEvaluationBlock:(BOOL (^)(id data))block { - RKObjectMappingDefinition *objectOrDynamicMapping = [self figureMapping:relationshipName]; + RKObjectMappingDefinition *objectOrDynamicMapping = [self objectMappingForRelationship:relationshipName]; NSString *sourceKeyPath = primaryKeyAttribute; - NSString *destinationKeyPath = [self figurePrimaryKeyPath:relationshipName]; + NSString *destinationKeyPath = [self primaryKeyPathForRelationship:relationshipName]; [self connectRelationship:relationshipName withMapping:objectOrDynamicMapping fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath usingEvaluationBlock:block]; } - @end diff --git a/Code/CoreData/RKManagedObjectMappingOperation.m b/Code/CoreData/RKManagedObjectMappingOperation.m index a4e72348..8fdfa1f6 100644 --- a/Code/CoreData/RKManagedObjectMappingOperation.m +++ b/Code/CoreData/RKManagedObjectMappingOperation.m @@ -32,25 +32,18 @@ @implementation RKManagedObjectMappingOperation -- (void)connectRelationship:(NSString *)relationshipName +- (void)connectRelationship:(RKObjectConnectionMapping *)connectionMapping { - RKObjectConnectionMapping *connectionMapping = [(RKManagedObjectMapping *)self.objectMapping mappingForConnection:relationshipName]; - NSAssert(connectionMapping, @"Unable to find relationship mapping '%@' to connect by primaryKey", relationshipName); - RKLogTrace(@"Connecting relationship '%@'", relationshipName); + RKLogTrace(@"Connecting relationship '%@'", connectionMapping.relationshipName); + id relatedObject = [connectionMapping findConnected:self.destinationObject]; - id relatedObject = [connectionMapping findConnected:relationshipName source:self.destinationObject]; - - if (relatedObject) { - if ([relatedObject isKindOfClass:[NSManagedObject class]]) { - // Sanity check the managed object contexts - NSAssert([[(NSManagedObject *)self.destinationObject managedObjectContext] isEqual:[(NSManagedObject *)relatedObject managedObjectContext]], nil); - } - [self.destinationObject setValue:relatedObject forKeyPath:relationshipName]; - RKLogDebug(@"Connected relationship '%@' to object '%@'", relationshipName, relatedObject); + if (relatedObject) { + [self.destinationObject setValue:relatedObject forKeyPath:connectionMapping.relationshipName]; + RKLogDebug(@"Connected relationship '%@' to object '%@'", connectionMapping.relationshipName, relatedObject); } else { RKManagedObjectMapping *objectMapping = (RKManagedObjectMapping *) connectionMapping.mapping; - RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@'", [[objectMapping entity] name], relationshipName); + RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@'", [[objectMapping entity] name], connectionMapping.relationshipName); } } @@ -58,15 +51,15 @@ { RKManagedObjectMapping *mapping = (RKManagedObjectMapping *)self.objectMapping; RKLogTrace(@"relationshipsAndPrimaryKeyAttributes: %@", mapping.connections); - for (NSString *relationshipName in mapping.connections) { + for (RKObjectConnectionMapping *connectionMapping in mapping.connections) { if (self.queue) { RKLogTrace(@"Enqueueing relationship connection using operation queue"); __block RKManagedObjectMappingOperation *selfRef = self; [self.queue addOperationWithBlock:^{ - [selfRef connectRelationship:relationshipName]; + [selfRef connectRelationship:connectionMapping]; }]; } else { - [self connectRelationship:relationshipName]; + [self connectRelationship:connectionMapping]; } } } diff --git a/Code/ObjectMapping/RKObjectConnectionMapping.h b/Code/ObjectMapping/RKObjectConnectionMapping.h index fd1751b0..e676a6e2 100644 --- a/Code/ObjectMapping/RKObjectConnectionMapping.h +++ b/Code/ObjectMapping/RKObjectConnectionMapping.h @@ -27,16 +27,47 @@ typedef id(^RKObjectConnectionBlock)(RKObjectConnectionMapping * mapping, id source); +// Defines the rules for connecting relationsips @interface RKObjectConnectionMapping : NSObject +@property (nonatomic, retain, readonly) NSString * relationshipName; @property (nonatomic, retain, readonly) NSString * sourceKeyPath; @property (nonatomic, retain, readonly) NSString * destinationKeyPath; @property (nonatomic, retain, readonly) RKObjectMappingDefinition * mapping; @property (nonatomic, retain, readonly) RKDynamicObjectMappingMatcher* matcher; -+ (RKObjectConnectionMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping; -+ (RKObjectConnectionMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)matcher withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping; -- (id)initFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)matcher withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping; +/** + Defines a mapping that is used to connect a source object relationship to + the appropriate target object(s). -- (id)findConnected:(NSString *)relationshipName source:(NSManagedObject *)source; + @param relationshipName The name of the relationship on the source object. + @param sourceKeyPath Specifies the path to an attribute on the source object that + contains the value that should be used to connect the relationship. This will generally + be a primary key or a foreign key value. + @param targetKeyPath Specifies the path to an attribute on the target object(s) that + must match the value of the sourceKeyPath attribute. + @param withMapping The mapping for the target object. + + @return A new instance of a RKObjectConnectionMapping. + */ ++ (RKObjectConnectionMapping*)mapping:(NSString *)relationshipName fromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping; + +/** + Defines a mapping that is used to connect a source object relationship to + the appropriate target object(s). This is similar to mapping:fromKeyPath:toKeyPath:withMapping: + (@see mapping:fromKeyPath:toKeyPath:withMapping:) but adds in an additional matcher parameter + that can be used to filter source objects. + + @return A new instance of a RKObjectConnectionMapping. + */ ++ (RKObjectConnectionMapping*)mapping:(NSString *)relationshipName fromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)matcher withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping; + +- (id)init:(NSString *)relationshipName fromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)matcher withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping; + +/** + Finds the connected objects for this relationship mapping. + + @return A single object, a set of 0 or more objects or nil. + */ +- (id)findConnected:(NSManagedObject *)source; @end diff --git a/Code/ObjectMapping/RKObjectConnectionMapping.m b/Code/ObjectMapping/RKObjectConnectionMapping.m index 5b37a4e4..f0858789 100644 --- a/Code/ObjectMapping/RKObjectConnectionMapping.m +++ b/Code/ObjectMapping/RKObjectConnectionMapping.m @@ -26,31 +26,34 @@ #import "RKDynamicObjectMappingMatcher.h" @interface RKObjectConnectionMapping() -@property (nonatomic, retain) NSString * sourceKeyPath; +@property (nonatomic, retain) NSString * relationshipName; @property (nonatomic, retain) NSString * destinationKeyPath; @property (nonatomic, retain) RKObjectMappingDefinition * mapping; @property (nonatomic, retain) RKDynamicObjectMappingMatcher* matcher; +@property (nonatomic, retain) NSString * sourceKeyPath; @end @implementation RKObjectConnectionMapping -@synthesize sourceKeyPath; +@synthesize relationshipName; @synthesize destinationKeyPath; @synthesize mapping; @synthesize matcher; +@synthesize sourceKeyPath; -+ (RKObjectConnectionMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping { - RKObjectConnectionMapping *mapping = [[self alloc] initFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:nil withMapping:objectOrDynamicMapping]; ++ (RKObjectConnectionMapping*)mapping:(NSString *)relationshipName fromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping { + RKObjectConnectionMapping *mapping = [[self alloc] init:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:nil withMapping:objectOrDynamicMapping]; return [mapping autorelease]; } -+ (RKObjectConnectionMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)matcher withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping { - RKObjectConnectionMapping *mapping = [[self alloc] initFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:matcher withMapping:objectOrDynamicMapping]; ++ (RKObjectConnectionMapping*)mapping:(NSString *)relationshipName fromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)matcher withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping { + RKObjectConnectionMapping *mapping = [[self alloc] init:relationshipName fromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath matcher:matcher withMapping:objectOrDynamicMapping]; return [mapping autorelease]; } -- (id)initFromKeyPath:(NSString*)aSourceKeyPath toKeyPath:(NSString*)aDestinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)aMatcher withMapping:(RKObjectMappingDefinition *)aObjectOrDynamicMapping { +- (id)init:(NSString *)aRelationshipName fromKeyPath:(NSString*)aSourceKeyPath toKeyPath:(NSString*)aDestinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)aMatcher withMapping:(RKObjectMappingDefinition *)aObjectOrDynamicMapping { self = [super init]; + self.relationshipName = aRelationshipName; self.sourceKeyPath = aSourceKeyPath; self.destinationKeyPath = aDestinationKeyPath; self.mapping = aObjectOrDynamicMapping; @@ -59,21 +62,22 @@ } - (id)copyWithZone:(NSZone *)zone { - return [[[self class] allocWithZone:zone] initFromKeyPath:self.sourceKeyPath toKeyPath:self.destinationKeyPath matcher:self.matcher withMapping:mapping]; + return [[[self class] allocWithZone:zone] init:self.relationshipName fromKeyPath:self.sourceKeyPath toKeyPath:self.destinationKeyPath matcher:self.matcher withMapping:mapping]; } - (void)dealloc { - self.sourceKeyPath = nil; + self.relationshipName = nil; self.destinationKeyPath = nil; self.mapping = nil; self.matcher = nil; + self.sourceKeyPath = nil; [super dealloc]; } -- (BOOL)isToMany:(NSString *)relationshipName source:(NSManagedObject *)source { +- (BOOL)isToMany:(NSManagedObject *)source { NSEntityDescription *entity = [source entity]; NSDictionary *relationships = [entity relationshipsByName]; - NSRelationshipDescription *relationship = [relationships objectForKey:relationshipName]; + NSRelationshipDescription *relationship = [relationships objectForKey:self.relationshipName]; return relationship.isToMany; } @@ -113,10 +117,10 @@ } } -- (id)findConnected:(NSString *)relationshipName source:(NSManagedObject *)source { +- (id)findConnected:(NSManagedObject *)source { if ([self checkMatcher:source]) { - BOOL isToMany = [self isToMany:relationshipName source:source]; + BOOL isToMany = [self isToMany:source]; id sourceValue = [source valueForKey:self.sourceKeyPath]; if (isToMany) { return [self findAllConnected:source sourceValue:sourceValue]; diff --git a/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m b/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m index a7398315..354d281c 100644 --- a/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m +++ b/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m @@ -54,8 +54,8 @@ assertThat(operation, is(instanceOf([RKObjectMappingOperation class]))); } -- (void)testShouldConnectRelationshipsByPrimaryKey -{ +- (void)testShouldConnectRelationshipsByPrimaryKey { + /* Connect a new human to a cat */ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore]; RKManagedObjectMapping *catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; @@ -65,8 +65,7 @@ RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; humanMapping.primaryKeyAttribute = @"railsID"; [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; + [humanMapping connectRelationship:@"favoriteCat" withMapping:catMapping fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID"]; // Create a cat to connect RKCat *cat = [RKCat object]; @@ -84,10 +83,44 @@ assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); } -- (void)testConnectRelationshipsDoesNotLeakMemory +- (void)testShouldConnectRelationshipsByPrimaryKeyReverse { RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore]; + RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; + + RKManagedObjectMapping *catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", @"railsID", nil]; + [catMapping connectRelationship:@"favoriteOfHumans" withMapping:humanMapping fromKeyPath:@"railsID" toKeyPath:@"favoriteCatID"]; + + // Create some humans to connect + RKHuman *blake = [RKHuman object]; + blake.name = @"Blake"; + blake.favoriteCatID = [NSNumber numberWithInt:31340]; + + RKHuman *jeremy = [RKHuman object]; + jeremy.name = @"Jeremy"; + jeremy.favoriteCatID = [NSNumber numberWithInt:31340]; + + [objectStore save:nil]; + + NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Asia", @"railsID", [NSNumber numberWithInt:31340], nil]; + RKCat *cat = [RKCat object]; + RKManagedObjectMappingOperation *operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:cat mapping:catMapping]; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(cat.favoriteOfHumans, isNot(nilValue())); + assertThat([cat.favoriteOfHumans valueForKeyPath:@"name"], containsInAnyOrder(blake.name, jeremy.name, nil)); +} + +- (void)testConnectRelationshipsDoesNotLeakMemory +{ + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + RKManagedObjectMapping *catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; catMapping.primaryKeyAttribute = @"railsID"; [catMapping mapAttributes:@"name", nil]; @@ -95,8 +128,7 @@ RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; humanMapping.primaryKeyAttribute = @"railsID"; [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; + [humanMapping connectRelationship:@"favoriteCat" withMapping:catMapping fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID"]; // Create a cat to connect RKCat *cat = [RKCat object]; @@ -125,8 +157,7 @@ RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; humanMapping.primaryKeyAttribute = @"railsID"; [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; + [humanMapping connectRelationship:@"favoriteCat" withMapping:catMapping fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID"]; // Create a cat to connect RKCat *cat = [RKCat object]; @@ -154,9 +185,8 @@ RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; humanMapping.primaryKeyAttribute = @"railsID"; - [humanMapping mapAttributes:@"name", @"favoriteCatID", @"catIDs", nil]; - [humanMapping mapRelationship:@"cats" withMapping:catMapping]; - [humanMapping connectRelationship:@"cats" withObjectForPrimaryKeyAttribute:@"catIDs"]; + [humanMapping mapAttributes:@"name", @"catIDs", nil]; + [humanMapping connectRelationship:@"cats" withMapping:catMapping fromKeyPath:@"catIDs" toKeyPath:@"railsID"]; // Create a couple of cats to connect RKCat *asia = [RKCat object]; @@ -181,6 +211,36 @@ assertThat([human.cats valueForKeyPath:@"name"], containsInAnyOrder(@"Asia", @"Reginald Royford Williams III", nil)); } +- (void)testShouldConnectRelationshipsByPrimaryKeyWithDifferentSourceAndDestinationKeyPathsReverse +{ + /* Connect a new cat to a human */ + RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"railsID", nil]; + + RKManagedObjectMapping *catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", @"humanId", nil]; + [catMapping connectRelationship:@"human" withMapping:humanMapping fromKeyPath:@"humanId" toKeyPath:@"railsID"]; + + // Create a human to connect + RKHuman *human = [RKHuman object]; + human.name = @"Blake"; + human.railsID = [NSNumber numberWithInt:31337]; + [objectStore save:nil]; + + NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Asia", @"humanId", [NSNumber numberWithInt:31337], nil]; + RKCat *cat = [RKCat object]; + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:cat mapping:catMapping]; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(cat.human, isNot(nilValue())); + assertThat(cat.human.name, is(equalTo(@"Blake"))); +} + - (void)testShouldLoadNestedHasManyRelationship { RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore]; @@ -274,8 +334,7 @@ RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; humanMapping.primaryKeyAttribute = @"railsID"; [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID" whenValueOfKeyPath:@"name" isEqualTo:@"Blake"]; + [humanMapping connectRelationship:@"favoriteCat" withMapping:catMapping fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" whenValueOfKeyPath:@"name" isEqualTo:@"Blake"]; // Create a cat to connect RKCat *cat = [RKCat object]; @@ -304,8 +363,7 @@ RKManagedObjectMapping *humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; humanMapping.primaryKeyAttribute = @"railsID"; [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID" whenValueOfKeyPath:@"name" isEqualTo:@"Jeff"]; + [humanMapping connectRelationship:@"favoriteCat" withMapping:catMapping fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" whenValueOfKeyPath:@"name" isEqualTo:@"Jeff"]; // Create a cat to connect RKCat *cat = [RKCat object]; @@ -360,8 +418,7 @@ RKManagedObjectMapping *childMapping = [RKManagedObjectMapping mappingForClass:[RKChild class] inManagedObjectStore:store]; [childMapping mapAttributes:@"fatherID", nil]; - [childMapping mapRelationship:@"father" withMapping:parentMapping]; - [childMapping connectRelationship:@"father" withObjectForPrimaryKeyAttribute:@"fatherID"]; + [childMapping connectRelationship:@"father" withMapping:parentMapping fromKeyPath:@"fatherID" toKeyPath:@"parentID"]; RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary @@ -508,4 +565,210 @@ assertThatInteger(childrenCount, is(equalToInteger(51))); } +/* Test deprecated connectionMapping API */ +- (void)testShouldConnectRelationshipsByPrimaryKeyDeprecated { + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", nil]; + + RKManagedObjectMapping* humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; + [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; + [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; + + // Create a cat to connect + RKCat* cat = [RKCat object]; + cat.name = @"Asia"; + cat.railsID = [NSNumber numberWithInt:31337]; + [objectStore save:nil]; + + NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; + RKHuman* human = [RKHuman object]; + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + NSError* error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.favoriteCat, isNot(nilValue())); + assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); +} + +- (void)testConnectRelationshipsDoesNotLeakMemoryDeprecated { + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", nil]; + + RKManagedObjectMapping* humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; + [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; + [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; + + // Create a cat to connect + RKCat* cat = [RKCat object]; + cat.name = @"Asia"; + cat.railsID = [NSNumber numberWithInt:31337]; + [objectStore save:nil]; + + NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; + RKHuman* human = [RKHuman object]; + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.queue = [RKMappingOperationQueue new]; + NSError* error = nil; + [operation performMapping:&error]; + + assertThatInteger([operation retainCount], is(equalToInteger(1))); +} + +- (void)testConnectionOfHasManyRelationshipsByPrimaryKeyDeprecated { + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", nil]; + + RKManagedObjectMapping* humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; + [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; + [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; + + // Create a cat to connect + RKCat* cat = [RKCat object]; + cat.name = @"Asia"; + cat.railsID = [NSNumber numberWithInt:31337]; + [objectStore save:nil]; + + NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; + RKHuman* human = [RKHuman object]; + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + NSError* error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.favoriteCat, isNot(nilValue())); + assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); +} + +- (void)testShouldConnectRelationshipsByPrimaryKeyWithDifferentSourceAndDestinationKeyPathsDeprecated { + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", nil]; + + RKManagedObjectMapping* humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", @"catIDs", nil]; + [humanMapping mapRelationship:@"cats" withMapping:catMapping]; + [humanMapping connectRelationship:@"cats" withObjectForPrimaryKeyAttribute:@"catIDs"]; + + // Create a couple of cats to connect + RKCat* asia = [RKCat object]; + asia.name = @"Asia"; + asia.railsID = [NSNumber numberWithInt:31337]; + + RKCat* roy = [RKCat object]; + roy.name = @"Reginald Royford Williams III"; + roy.railsID = [NSNumber numberWithInt:31338]; + + [objectStore save:nil]; + + NSArray *catIDs = [NSArray arrayWithObjects:[NSNumber numberWithInt:31337], [NSNumber numberWithInt:31338], nil]; + NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"catIDs", catIDs, nil]; + RKHuman* human = [RKHuman object]; + + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + NSError* error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.cats, isNot(nilValue())); + assertThat([human.cats valueForKeyPath:@"name"], containsInAnyOrder(@"Asia", @"Reginald Royford Williams III", nil)); +} + +- (void)testShouldDynamicallyConnectRelationshipsByPrimaryKeyWhenMatchingSucceedsDeprecated { + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", nil]; + + RKManagedObjectMapping* humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; + [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; + [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID" whenValueOfKeyPath:@"name" isEqualTo:@"Blake"]; + + // Create a cat to connect + RKCat* cat = [RKCat object]; + cat.name = @"Asia"; + cat.railsID = [NSNumber numberWithInt:31337]; + [objectStore save:nil]; + + NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; + RKHuman* human = [RKHuman object]; + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + NSError* error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.favoriteCat, isNot(nilValue())); + assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); +} + +- (void)testShouldNotDynamicallyConnectRelationshipsByPrimaryKeyWhenMatchingFailsDeprecated { + RKManagedObjectStore* objectStore = [RKTestFactory managedObjectStore]; + + RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class] inManagedObjectStore:objectStore]; + catMapping.primaryKeyAttribute = @"railsID"; + [catMapping mapAttributes:@"name", nil]; + + RKManagedObjectMapping* humanMapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:objectStore]; + humanMapping.primaryKeyAttribute = @"railsID"; + [humanMapping mapAttributes:@"name", @"favoriteCatID", nil]; + [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; + [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID" whenValueOfKeyPath:@"name" isEqualTo:@"Jeff"]; + + // Create a cat to connect + RKCat* cat = [RKCat object]; + cat.name = @"Asia"; + cat.railsID = [NSNumber numberWithInt:31337]; + [objectStore save:nil]; + + NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; + RKHuman* human = [RKHuman object]; + RKManagedObjectMappingOperation* operation = [[RKManagedObjectMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + NSError* error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.favoriteCat, is(nilValue())); +} + +- (void)testShouldConnectRelationshipsByPrimaryKeyRegardlessOfOrderDeprecated { + RKManagedObjectStore *store = [RKTestFactory managedObjectStore]; + RKManagedObjectMapping* parentMapping = [RKManagedObjectMapping mappingForClass:[RKParent class] inManagedObjectStore:store]; + [parentMapping mapAttributes:@"parentID", nil]; + parentMapping.primaryKeyAttribute = @"parentID"; + + RKManagedObjectMapping* childMapping = [RKManagedObjectMapping mappingForClass:[RKChild class] inManagedObjectStore:store]; + [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 = [RKTestFixture parsedObjectWithContentsOfFixture:@"ConnectingParents.json"]; + 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 diff --git a/Tests/Models/RKCat.h b/Tests/Models/RKCat.h index 214eaf60..45d0e2b8 100644 --- a/Tests/Models/RKCat.h +++ b/Tests/Models/RKCat.h @@ -39,5 +39,6 @@ @property (nonatomic, retain) NSDate *updatedAt; @property (nonatomic, retain) RKHuman *human; +@property (nonatomic, retain) NSSet *favoriteOfHumans; @end diff --git a/Tests/Models/RKCat.m b/Tests/Models/RKCat.m index e64a913c..d7bf45e6 100644 --- a/Tests/Models/RKCat.m +++ b/Tests/Models/RKCat.m @@ -28,6 +28,7 @@ @dynamic birthYear; @dynamic color; @dynamic createdAt; +@dynamic favoriteOfHumans; @dynamic humanId; @dynamic name; @dynamic nickName; diff --git a/Tests/Models/RKHuman.h b/Tests/Models/RKHuman.h index 21d1b0ed..c2bedec1 100644 --- a/Tests/Models/RKHuman.h +++ b/Tests/Models/RKHuman.h @@ -36,6 +36,7 @@ @property (nonatomic, retain) NSArray *favoriteColors; @property (nonatomic, retain) NSSet *cats; +@property (nonatomic, retain) NSNumber *favoriteCatID; @property (nonatomic, retain) RKCat *favoriteCat; @property (nonatomic, retain) NSArray *catIDs; diff --git a/Tests/Models/RKHuman.m b/Tests/Models/RKHuman.m index 6196e210..38f3963a 100644 --- a/Tests/Models/RKHuman.m +++ b/Tests/Models/RKHuman.m @@ -34,6 +34,7 @@ @dynamic updatedAt; @dynamic favoriteColors; +@dynamic favoriteCatID; @dynamic favoriteCat; @dynamic cats; @dynamic catIDs;