From 2a434375f072743217fcb19538a40145334c87d5 Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Wed, 16 May 2012 21:34:54 -0600 Subject: [PATCH] Reimplement connectRelationship as described in issue #593. --- Code/CoreData/RKManagedObjectMapping.h | 44 +++--- Code/CoreData/RKManagedObjectMapping.m | 112 +++++++++++---- .../RKManagedObjectMappingOperation.m | 69 ++------- .../ObjectMapping/RKObjectConnectionMapping.h | 42 ++++++ .../ObjectMapping/RKObjectConnectionMapping.m | 132 ++++++++++++++++++ 5 files changed, 295 insertions(+), 104 deletions(-) create mode 100644 Code/ObjectMapping/RKObjectConnectionMapping.h create mode 100644 Code/ObjectMapping/RKObjectConnectionMapping.m diff --git a/Code/CoreData/RKManagedObjectMapping.h b/Code/CoreData/RKManagedObjectMapping.h index dbf2749f..5dfdb689 100644 --- a/Code/CoreData/RKManagedObjectMapping.h +++ b/Code/CoreData/RKManagedObjectMapping.h @@ -20,7 +20,7 @@ #import #import "RKObjectMapping.h" -//#import "RKManagedObjectStore.h" +#import "RKObjectConnectionMapping.h" @class RKManagedObjectStore; @@ -29,7 +29,7 @@ entity. */ @interface RKManagedObjectMapping : RKObjectMapping { - NSMutableDictionary *_relationshipToPrimaryKeyMappings; + NSMutableDictionary *_connections; } /** @@ -72,16 +72,20 @@ @property (nonatomic, retain) NSString *primaryKeyAttribute; /** - Returns a dictionary containing Core Data relationships and attribute pairs containing - the primary key for + Returns a dictionary containing Core Data connections */ -@property (nonatomic, readonly) NSDictionary *relationshipsAndPrimaryKeyAttributes; +@property (nonatomic, readonly) NSDictionary *connections; /** The RKManagedObjectStore containing the Core Data entity being mapped */ @property (nonatomic, readonly) RKManagedObjectStore *objectStore; +/** + Returns the RKObjectRelationshipMapping connection for the specified relationship. + */ +- (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. @@ -89,24 +93,23 @@ For example, given a Project object associated with a User, where the 'user' relationship is specified by a userID property on the managed object: - [mapping connectRelationship:@"user" withObjectForPrimaryKeyAttribute:@"userID"]; + [mapping connectRelationship:@"user" withMapping:userMapping fromKeyPath:@"userId" toKeyPath:@"id"]; Will hydrate the 'user' association on the managed object with the object in the local object graph having the primary key specified in the managed object's userID property. + You can also do the reverse. Given a User object associated with a Project, with a + 'project' relationship: + + [mapping connectRelationship:@"project" withMapping:projectMapping fromKeyPath:@"id" toKeyPath:@"userId"]; + In effect, this approach allows foreign key relationships between managed objects to be automatically maintained from the server to the underlying Core Data object graph. */ -- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute; +- (void)connectRelationship:(NSString *)relationshipName withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping fromKeyPath:(NSString *)sourceKeyPath toKeyPath:(NSString *)destinationKeyPath; -/** - Connects relationships using the primary key values contained in the specified attribute. This method is - a short-cut for repeated invocation of `connectRelationship:withObjectForPrimaryKeyAttribute:`. - @see connectRelationship:withObjectForPrimaryKeyAttribute: - */ -- (void)connectRelationshipsWithObjectsForPrimaryKeyAttributes:(NSString *)firstRelationshipName, ... NS_REQUIRES_NIL_TERMINATION; /** Conditionally connect a relationship of the object being mapped when the object being mapped has @@ -115,7 +118,7 @@ For example, given a Project object associated with a User, where the 'admin' relationship is specified by a adminID property on the managed object: - [mapping connectRelationship:@"admin" withObjectForPrimaryKeyAttribute:@"adminID" whenValueOfKeyPath:@"userType" isEqualTo:@"Admin"]; + [mapping connectRelationship:@"admin" withMapping:userMapping fromKeyPath:@"adminId" toKeyPath:@"id" whenValueOfKeyPath:@"userType" isEqualTo:@"Admin"]; Will hydrate the 'admin' association on the managed object with the object in the local object graph having the primary key specified in the managed object's @@ -124,7 +127,8 @@ @see connectRelationship:withObjectForPrimaryKeyAttribute: */ -- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute whenValueOfKeyPath:(NSString *)keyPath isEqualTo:(id)value; +- (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 @@ -134,7 +138,7 @@ For example, given a Project object associated with a User, where the 'admin' relationship is specified by a adminID property on the managed object: - [mapping connectRelationship:@"admin" withObjectForPrimaryKeyAttribute:@"adminID" usingEvaluationBlock:^(id data) { + [mapping connectRelationship:@"admin" withMapping:userMapping fromKeyPath:@"adminId" toKeyPath:@"adminID" usingEvaluationBlock:^(id data) { return [User isAuthenticated]; }]; @@ -145,7 +149,8 @@ @see connectRelationship:withObjectForPrimaryKeyAttribute: */ -- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute usingEvaluationBlock:(BOOL (^)(id data))block; +- (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 @@ -158,4 +163,9 @@ */ - (id)defaultValueForMissingAttribute:(NSString *)attributeName; +/* Deprecated */ +- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute DEPRECATED_ATTRIBUTE; +- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute whenValueOfKeyPath:(NSString *)keyPath isEqualTo:(id)value DEPRECATED_ATTRIBUTE; +- (void)connectRelationshipsWithObjectsForPrimaryKeyAttributes:(NSString *)firstRelationshipName, ... NS_REQUIRES_NIL_TERMINATION DEPRECATED_ATTRIBUTE; +- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute usingEvaluationBlock:(BOOL (^)(id data))block DEPRECATED_ATTRIBUTE; @end diff --git a/Code/CoreData/RKManagedObjectMapping.m b/Code/CoreData/RKManagedObjectMapping.m index 44392719..03d24864 100644 --- a/Code/CoreData/RKManagedObjectMapping.m +++ b/Code/CoreData/RKManagedObjectMapping.m @@ -30,6 +30,10 @@ #undef RKLogComponent #define RKLogComponent lcl_cRestKitCoreData +// Implemented in RKObjectMappingOperation +BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue); + + @implementation RKManagedObjectMapping @synthesize entity = _entity; @@ -82,7 +86,7 @@ { self = [super init]; if (self) { - _relationshipToPrimaryKeyMappings = [[NSMutableDictionary alloc] init]; + _connections = [[NSMutableDictionary alloc] init]; } return self; @@ -94,48 +98,60 @@ [self removeObserver:self forKeyPath:@"primaryKeyAttribute"]; [_entity release]; - [_relationshipToPrimaryKeyMappings release]; + [_connections release]; [super dealloc]; } -- (NSDictionary *)relationshipsAndPrimaryKeyAttributes +- (NSDictionary *)connections { - return _relationshipToPrimaryKeyMappings; + return _connections; } -- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute +- (RKObjectRelationshipMapping *)mappingForConnection:(NSString *)relationshipName { - NSAssert([_relationshipToPrimaryKeyMappings objectForKey:relationshipName] == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", relationshipName); - [_relationshipToPrimaryKeyMappings setObject:primaryKeyAttribute forKey:relationshipName]; + return [_connections objectForKey:relationshipName]; } -- (void)connectRelationshipsWithObjectsForPrimaryKeyAttributes:(NSString *)firstRelationshipName, ... -{ - va_list args; - va_start(args, firstRelationshipName); - for (NSString *relationshipName = firstRelationshipName; relationshipName != nil; relationshipName = va_arg(args, NSString *)) { - NSString *primaryKeyAttribute = va_arg(args, NSString *); - NSAssert(primaryKeyAttribute != nil, @"Cannot connect a relationship without an attribute containing the primary key"); - [self connectRelationship:relationshipName withObjectForPrimaryKeyAttribute:primaryKeyAttribute]; - // TODO: Raise proper exception here, argument error... - } - va_end(args); +- (RKObjectMappingDefinition *)figureMapping:(NSString *)relationshipName { + RKObjectRelationshipMapping *relationshipMapping = [self mappingForRelationship:relationshipName]; + return relationshipMapping.mapping; } -- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute whenValueOfKeyPath:(NSString *)keyPath isEqualTo:(id)value +- (NSString *)figurePrimaryKeyPath:(NSString *)relationshipName { - NSAssert([_relationshipToPrimaryKeyMappings objectForKey:relationshipName] == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", relationshipName); - RKDynamicObjectMappingMatcher *matcher = [[RKDynamicObjectMappingMatcher alloc] initWithKey:keyPath value:value primaryKeyAttribute:primaryKeyAttribute]; - [_relationshipToPrimaryKeyMappings setObject:matcher forKey:relationshipName]; - [matcher release]; + RKObjectMappingDefinition* mappingDef = [self figureMapping:relationshipName]; + RKManagedObjectMapping *objectMapping = (RKManagedObjectMapping *) mappingDef; + return [objectMapping primaryKeyAttribute]; } -- (void)connectRelationship:(NSString *)relationshipName withObjectForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute usingEvaluationBlock:(BOOL (^)(id data))block +- (void)addConnectionMapping:(NSString *)relationshipName withMapping:(RKObjectConnectionMapping *)mapping { - NSAssert([_relationshipToPrimaryKeyMappings objectForKey:relationshipName] == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", relationshipName); - RKDynamicObjectMappingMatcher *matcher = [[RKDynamicObjectMappingMatcher alloc] initWithPrimaryKeyAttribute:primaryKeyAttribute evaluationBlock:block]; - [_relationshipToPrimaryKeyMappings setObject:matcher forKey:relationshipName]; - [matcher release]; + NSAssert([_connections objectForKey:relationshipName] == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", 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]; +} + +- (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]; +} + +- (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]; +} + +- (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]; } - (id)defaultValueForMissingAttribute:(NSString *)attributeName @@ -232,4 +248,44 @@ } } } + +/* Deprecated */ +- (void)connectRelationship:(NSString*)relationshipName withObjectForPrimaryKeyAttribute:(NSString*)primaryKeyAttribute +{ + RKObjectMappingDefinition *objectOrDynamicMapping = [self figureMapping:relationshipName]; + NSString *sourceKeyPath = primaryKeyAttribute; + NSString *destinationKeyPath = [self figurePrimaryKeyPath:relationshipName]; + + RKObjectConnectionMapping* mapping = [RKObjectConnectionMapping mappingFromKeyPath:sourceKeyPath toKeyPath:destinationKeyPath withMapping:objectOrDynamicMapping]; + [self addConnectionMapping:relationshipName withMapping:mapping]; +} + +- (void)connectRelationshipsWithObjectsForPrimaryKeyAttributes:(NSString*)firstRelationshipName, ... +{ + va_list args; + va_start(args, firstRelationshipName); + for (NSString* relationshipName = firstRelationshipName; relationshipName != nil; relationshipName = va_arg(args, NSString*)) { + NSString* primaryKeyAttribute = va_arg(args, NSString*); + [self connectRelationship:relationshipName withObjectForPrimaryKeyAttribute:primaryKeyAttribute]; + // TODO: Raise proper exception here, argument error... + } + va_end(args); +} + +- (void)connectRelationship:(NSString*)relationshipName withObjectForPrimaryKeyAttribute:(NSString*)primaryKeyAttribute whenValueOfKeyPath:(NSString*)keyPath isEqualTo:(id)value +{ + RKObjectMappingDefinition *objectOrDynamicMapping = [self figureMapping:relationshipName]; + NSString *sourceKeyPath = primaryKeyAttribute; + NSString *destinationKeyPath = [self figurePrimaryKeyPath: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]; + NSString *sourceKeyPath = primaryKeyAttribute; + NSString *destinationKeyPath = [self figurePrimaryKeyPath: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 c9ad3385..a4e72348 100644 --- a/Code/CoreData/RKManagedObjectMappingOperation.m +++ b/Code/CoreData/RKManagedObjectMappingOperation.m @@ -34,80 +34,31 @@ - (void)connectRelationship:(NSString *)relationshipName { - NSDictionary *relationshipsAndPrimaryKeyAttributes = [(RKManagedObjectMapping *)self.objectMapping relationshipsAndPrimaryKeyAttributes]; - id primaryKeyObject = [relationshipsAndPrimaryKeyAttributes objectForKey:relationshipName]; - NSString *primaryKeyAttribute = nil; - if ([primaryKeyObject isKindOfClass:[RKDynamicObjectMappingMatcher class]]) { - RKLogTrace(@"Found a dynamic matcher attempting to connect relationshipName: %@", relationshipName); - RKDynamicObjectMappingMatcher *matcher = (RKDynamicObjectMappingMatcher *)primaryKeyObject; - if ([matcher isMatchForData:self.destinationObject]) { - primaryKeyAttribute = matcher.primaryKeyAttribute; - RKLogTrace(@"Dynamic matched succeeded. Proceeding to connect relationshipName '%@' using primaryKeyAttribute '%@'", relationshipName, primaryKeyAttribute); - } else { - RKLogTrace(@"Dynamic matcher match failed. Skipping connection of relationshipName: %@", relationshipName); - return; - } - } else if ([primaryKeyObject isKindOfClass:[NSString class]]) { - primaryKeyAttribute = (NSString *)primaryKeyObject; - } - NSAssert(primaryKeyAttribute, @"Cannot connect relationship without primaryKeyAttribute"); + RKObjectConnectionMapping *connectionMapping = [(RKManagedObjectMapping *)self.objectMapping mappingForConnection:relationshipName]; + NSAssert(connectionMapping, @"Unable to find relationship mapping '%@' to connect by primaryKey", relationshipName); + RKLogTrace(@"Connecting relationship '%@'", relationshipName); - RKObjectRelationshipMapping *relationshipMapping = [self.objectMapping mappingForRelationship:relationshipName]; - 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; - } - RKManagedObjectMapping *objectMapping = (RKManagedObjectMapping *)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 = nil; - if ([valueOfLocalPrimaryKeyAttribute conformsToProtocol:@protocol(NSFastEnumeration)]) { - RKLogTrace(@"Connecting has-many relationship at keyPath '%@' to object with primaryKey attribute '%@'", relationshipName, primaryKeyAttributeOfRelatedObject); - // Implemented for issue 284 - https://github.com/RestKit/RestKit/issues/284 - relatedObject = [NSMutableSet set]; - NSObject *cache = [[(RKManagedObjectMapping *)[self objectMapping] objectStore] cacheStrategy]; - for (id foreignKey in valueOfLocalPrimaryKeyAttribute) { - id searchResult = [cache findInstanceOfEntity:objectMapping.entity withPrimaryKeyAttribute:primaryKeyAttributeOfRelatedObject value:foreignKey inManagedObjectContext:[[(RKManagedObjectMapping *)[self objectMapping] objectStore] managedObjectContextForCurrentThread]]; - if (searchResult) { - [relatedObject addObject:searchResult]; - } - } - } else { - RKLogTrace(@"Connecting has-one relationship at keyPath '%@' to object with primaryKey attribute '%@'", relationshipName, primaryKeyAttributeOfRelatedObject); + id relatedObject = [connectionMapping findConnected:relationshipName source:self.destinationObject]; - // Normal foreign key - NSObject *cache = [[(RKManagedObjectMapping *)[self objectMapping] objectStore] cacheStrategy]; - relatedObject = [cache findInstanceOfEntity:objectMapping.entity withPrimaryKeyAttribute:primaryKeyAttributeOfRelatedObject value:valueOfLocalPrimaryKeyAttribute inManagedObjectContext:[self.destinationObject managedObjectContext]]; - } if (relatedObject) { - RKLogDebug(@"Connected relationship '%@' to object with primary key value '%@': %@", relationshipName, valueOfLocalPrimaryKeyAttribute, relatedObject); - } else { - RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@' with primary key value '%@'", [[objectMapping entity] name], relationshipName, valueOfLocalPrimaryKeyAttribute); - } if ([relatedObject isKindOfClass:[NSManagedObject class]]) { // Sanity check the managed object contexts NSAssert([[(NSManagedObject *)self.destinationObject managedObjectContext] isEqual:[(NSManagedObject *)relatedObject managedObjectContext]], nil); } - RKLogTrace(@"setValue of %@ forKeyPath %@", relatedObject, relationshipName); [self.destinationObject setValue:relatedObject forKeyPath:relationshipName]; + RKLogDebug(@"Connected relationship '%@' to object '%@'", relationshipName, relatedObject); } else { - RKLogTrace(@"Failed to find primary key value for attribute '%@'", primaryKeyAttribute); + RKManagedObjectMapping *objectMapping = (RKManagedObjectMapping *) connectionMapping.mapping; + RKLogDebug(@"Failed to find instance of '%@' to connect relationship '%@'", [[objectMapping entity] name], relationshipName); } } - (void)connectRelationships { - NSDictionary *relationshipsAndPrimaryKeyAttributes = [(RKManagedObjectMapping *)self.objectMapping relationshipsAndPrimaryKeyAttributes]; - RKLogTrace(@"relationshipsAndPrimaryKeyAttributes: %@", relationshipsAndPrimaryKeyAttributes); - for (NSString *relationshipName in relationshipsAndPrimaryKeyAttributes) { + RKManagedObjectMapping *mapping = (RKManagedObjectMapping *)self.objectMapping; + RKLogTrace(@"relationshipsAndPrimaryKeyAttributes: %@", mapping.connections); + for (NSString *relationshipName in mapping.connections) { if (self.queue) { RKLogTrace(@"Enqueueing relationship connection using operation queue"); __block RKManagedObjectMappingOperation *selfRef = self; diff --git a/Code/ObjectMapping/RKObjectConnectionMapping.h b/Code/ObjectMapping/RKObjectConnectionMapping.h new file mode 100644 index 00000000..fd1751b0 --- /dev/null +++ b/Code/ObjectMapping/RKObjectConnectionMapping.h @@ -0,0 +1,42 @@ +// +// RKObjectConnectionMapping.h +// RestKit +// +// Created by Charlie Savage on 5/15/12. +// Copyright (c) 2009-2012 RestKit. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import +#import "RKObjectMappingDefinition.h" + +@class RKObjectConnectionMapping; +@class RKDynamicObjectMappingMatcher; + +typedef id(^RKObjectConnectionBlock)(RKObjectConnectionMapping * mapping, id source); + +@interface RKObjectConnectionMapping : NSObject + +@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; + +- (id)findConnected:(NSString *)relationshipName source:(NSManagedObject *)source; +@end diff --git a/Code/ObjectMapping/RKObjectConnectionMapping.m b/Code/ObjectMapping/RKObjectConnectionMapping.m new file mode 100644 index 00000000..5b37a4e4 --- /dev/null +++ b/Code/ObjectMapping/RKObjectConnectionMapping.m @@ -0,0 +1,132 @@ +// +// RKObjectConnectionMapping.m +// RestKit +// +// Created by Charlie Savage on 5/15/12. +// Copyright (c) 2009-2012 RestKit. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "RKObjectConnectionMapping.h" +#import "NSManagedObject+ActiveRecord.h" +#import "RKManagedObjectMapping.h" +#import "RKObjectManager.h" +#import "RKManagedObjectCaching.h" +#import "RKDynamicObjectMappingMatcher.h" + +@interface RKObjectConnectionMapping() +@property (nonatomic, retain) NSString * sourceKeyPath; +@property (nonatomic, retain) NSString * destinationKeyPath; +@property (nonatomic, retain) RKObjectMappingDefinition * mapping; +@property (nonatomic, retain) RKDynamicObjectMappingMatcher* matcher; +@end + +@implementation RKObjectConnectionMapping + +@synthesize sourceKeyPath; +@synthesize destinationKeyPath; +@synthesize mapping; +@synthesize matcher; + ++ (RKObjectConnectionMapping*)mappingFromKeyPath:(NSString*)sourceKeyPath toKeyPath:(NSString*)destinationKeyPath withMapping:(RKObjectMappingDefinition *)objectOrDynamicMapping { + RKObjectConnectionMapping *mapping = [[self alloc] initFromKeyPath: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]; + return [mapping autorelease]; +} + +- (id)initFromKeyPath:(NSString*)aSourceKeyPath toKeyPath:(NSString*)aDestinationKeyPath matcher:(RKDynamicObjectMappingMatcher *)aMatcher withMapping:(RKObjectMappingDefinition *)aObjectOrDynamicMapping { + self = [super init]; + self.sourceKeyPath = aSourceKeyPath; + self.destinationKeyPath = aDestinationKeyPath; + self.mapping = aObjectOrDynamicMapping; + self.matcher = aMatcher; + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + return [[[self class] allocWithZone:zone] initFromKeyPath:self.sourceKeyPath toKeyPath:self.destinationKeyPath matcher:self.matcher withMapping:mapping]; +} + +- (void)dealloc { + self.sourceKeyPath = nil; + self.destinationKeyPath = nil; + self.mapping = nil; + self.matcher = nil; + [super dealloc]; +} + +- (BOOL)isToMany:(NSString *)relationshipName source:(NSManagedObject *)source { + NSEntityDescription *entity = [source entity]; + NSDictionary *relationships = [entity relationshipsByName]; + NSRelationshipDescription *relationship = [relationships objectForKey:relationshipName]; + return relationship.isToMany; +} + +- (NSManagedObject*)findOneConnected:(NSManagedObject *)source sourceValue:(id)sourceValue { + RKManagedObjectMapping* objectMapping = (RKManagedObjectMapping *)self.mapping; + NSObject *cache = [[objectMapping objectStore] cacheStrategy]; + NSManagedObjectContext *context = [[objectMapping objectStore] managedObjectContextForCurrentThread]; + return [cache findInstanceOfEntity:objectMapping.entity withPrimaryKeyAttribute:self.destinationKeyPath value:sourceValue inManagedObjectContext:context]; +} + +- (NSMutableSet*)findAllConnected:(NSManagedObject *)source sourceValue:(id)sourceValue { + NSMutableSet *result = [NSMutableSet set]; + + if ([sourceValue conformsToProtocol:@protocol(NSFastEnumeration)]) { + for (id value in sourceValue) { + id searchResult = [self findOneConnected:source sourceValue:value]; + if (searchResult) { + [result addObject:searchResult]; + } + } + } + else { + id searchResult = [self findOneConnected:source sourceValue:sourceValue]; + if (searchResult) { + [result addObject:searchResult]; + } + } + return result; +} + +- (BOOL)checkMatcher:(NSManagedObject *)source { + if (!matcher) { + return YES; + } + else { + return [matcher isMatchForData:source]; + } +} + +- (id)findConnected:(NSString *)relationshipName source:(NSManagedObject *)source { + if ([self checkMatcher:source]) + { + BOOL isToMany = [self isToMany:relationshipName source:source]; + id sourceValue = [source valueForKey:self.sourceKeyPath]; + if (isToMany) { + return [self findAllConnected:source sourceValue:sourceValue]; + } + else { + return [self findOneConnected:source sourceValue:sourceValue]; + } + } + else { + return nil; + } +} +@end