Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Major overhaul to the Core Data managed object identification and rel…

…ationship connection support.

* Replaces primary key with `RKEntityIdentifier`
* Add support for use of compound keys for object identification
* Refactor `RKConnectionMapping` to `RKConnectionDescription` and add support for connecting with multiple attributes
* Clarify naming of representation key methods to better match naming conventions
* Add type transformation support for object identification
* Greatly expand test coverage for object identification
* Drop the `NSEntityDescription` category
* Simplify the `RKManagedObjectCaching` protocol
* Add compound key support to the Fetch Request and In Memory Cache implementations
* Replace Kiwi with Specta for tests where contexts are helpful for organization
* Rename `defaultValueForMissingAttribute` to `defaultValueForAttribute`
  • Loading branch information...
commit 8dc54a89b2f4a15f541200fbc99686e0f285e0cb 1 parent f3a853e
@blakewatters blakewatters authored
Showing with 2,112 additions and 898 deletions.
  1. +0 −1  Code/CoreData.h
  2. +0 −1  Code/CoreData/NSManagedObject+RKAdditions.m
  3. +21 −21 Code/CoreData/NSManagedObjectContext+RKAdditions.h
  4. +34 −34 Code/CoreData/NSManagedObjectContext+RKAdditions.m
  5. +26 −0 Code/CoreData/RKConnectionDescription.h
  6. +129 −0 Code/CoreData/RKConnectionDescription.m
  7. +4 −4 Code/CoreData/{RKRelationshipConnectionOperation.h → RKConnectionOperation.h}
  8. +65 −65 Code/CoreData/{RKRelationshipConnectionOperation.m → RKConnectionOperation.m}
  9. +13 −16 Code/CoreData/RKEntityByAttributeCache.h
  10. +73 −66 Code/CoreData/RKEntityByAttributeCache.m
  11. +12 −14 Code/CoreData/RKEntityCache.h
  12. +25 −23 Code/CoreData/RKEntityCache.m
  13. +39 −0 Code/CoreData/RKEntityIdentifier.h
  14. +117 −0 Code/CoreData/RKEntityIdentifier.m
  15. +56 −38 Code/CoreData/RKEntityMapping.h
  16. +96 −46 Code/CoreData/RKEntityMapping.m
  17. +43 −39 Code/CoreData/RKFetchRequestManagedObjectCache.m
  18. +14 −29 Code/CoreData/RKInMemoryManagedObjectCache.m
  19. +15 −8 Code/CoreData/RKManagedObjectCaching.h
  20. +115 −59 Code/CoreData/RKManagedObjectMappingOperationDataSource.m
  21. +0 −2  Code/Network/RKObjectParameterization.m
  22. +5 −5 Code/ObjectMapping/RKMappingOperation.h
  23. +69 −40 Code/ObjectMapping/RKMappingOperation.m
  24. +15 −19 Code/ObjectMapping/RKObjectMapping.h
  25. +22 −20 Code/ObjectMapping/RKObjectMapping.m
  26. +18 −0 Code/ObjectMapping/RKValueTransformers.h
  27. +53 −0 Code/ObjectMapping/RKValueTransformers.m
  28. +8 −0 Code/Support/RKMacros.h
  29. +0 −17 Code/Support/RKPathMatcher.h
  30. +0 −5 Code/Support/RKPathMatcher.m
  31. +27 −5 Code/Testing/RKMappingTest.m
  32. +1 −1  Gemfile
  33. +7 −9 Gemfile.lock
  34. +3 −3 Podfile
  35. +12 −9 Podfile.lock
  36. +118 −63 RestKit.xcodeproj/project.pbxproj
  37. +86 −0 Tests/Logic/CoreData/RKConnectionDescriptionTest.m
  38. +15 −7 Tests/Logic/CoreData/RKEntityByAttributeCacheTest.m
  39. +24 −24 Tests/Logic/CoreData/RKEntityCacheTest.m
  40. +225 −0 Tests/Logic/CoreData/RKEntityIdentifierTest.m
  41. +171 −38 Tests/Logic/CoreData/RKEntityMappingTest.m
  42. +8 −8 Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m
  43. +2 −2 Tests/Logic/CoreData/RKManagedObjectLoaderTest.m
  44. +235 −69 Tests/Logic/CoreData/RKManagedObjectMappingOperationDataSourceTest.m
  45. +35 −35 Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m
  46. +19 −16 Tests/Logic/CoreData/RKRelationshipConnectionOperationTest.m
  47. +27 −27 Tests/Logic/Network/RKResponseDescriptorTest.m
  48. +5 −5 Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m
  49. +4 −4 Tests/Logic/ObjectMapping/RKObjectMappingTest.m
  50. +1 −1  Tests/Logic/Search/RKSearchIndexerTest.m
View
1  Code/CoreData.h
@@ -31,4 +31,3 @@
#import "RKPropertyInspector+CoreData.h"
#import "NSManagedObjectContext+RKAdditions.h"
#import "NSManagedObject+RKAdditions.h"
-#import "NSEntityDescription+RKAdditions.h"
View
1  Code/CoreData/NSManagedObject+RKAdditions.m
@@ -10,7 +10,6 @@
#import "NSManagedObjectContext+RKAdditions.h"
#import "RKLog.h"
#import "RKManagedObjectStore.h"
-#import "NSEntityDescription+RKAdditions.h"
@implementation NSManagedObject (RKAdditions)
View
42 Code/CoreData/NSManagedObjectContext+RKAdditions.h
@@ -64,27 +64,27 @@
*/
- (NSUInteger)countForEntityForName:(NSString *)entityName predicate:(NSPredicate *)predicate error:(NSError **)error;
-///----------------------------------------------
-/// @name Fetching Managed Objects by Primary Key
-///----------------------------------------------
-
-/**
- Fetches a single managed object for the given entity with the given value for the primary key attribute in the receiver.
-
- @param entity The entity of the managed object to be retrieved by primary key.
- @param primaryKeyValue The value for the entity's primary key attribute.
- @return The managed object with the primary key attribute equal to the given value or nil if none was found.
- */
-- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
-
-/**
- Fetches a single managed object for the entity for the given name with the given value for the primary key attribute in the receiver.
-
- @param entityName The name of the entity of the managed object to be retrieved by primary key.
- @param primaryKeyValue The value for the receiving entity's primary key attribute.
- @return The managed object with the primary key attribute equal to the given value or nil if none was found.
- */
-- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
+/////----------------------------------------------
+///// @name Fetching Managed Objects by Primary Key
+/////----------------------------------------------
+//
+///**
+// Fetches a single managed object for the given entity with the given value for the primary key attribute in the receiver.
+//
+// @param entity The entity of the managed object to be retrieved by primary key.
+// @param primaryKeyValue The value for the entity's primary key attribute.
+// @return The managed object with the primary key attribute equal to the given value or nil if none was found.
+// */
+//- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
+//
+///**
+// Fetches a single managed object for the entity for the given name with the given value for the primary key attribute in the receiver.
+//
+// @param entityName The name of the entity of the managed object to be retrieved by primary key.
+// @param primaryKeyValue The value for the receiving entity's primary key attribute.
+// @return The managed object with the primary key attribute equal to the given value or nil if none was found.
+// */
+//- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue;
///-------------------------------------------------
/// @name Saving the Context to the Persistent Store
View
68 Code/CoreData/NSManagedObjectContext+RKAdditions.m
@@ -37,40 +37,40 @@ - (NSUInteger)countForEntityForName:(NSString *)entityName predicate:(NSPredicat
return [self countForFetchRequest:fetchRequest error:error];
}
-- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue
-{
- NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
- if (! predicate) {
- RKLogWarning(@"Attempt to fetchObjectForEntity for entity with nil primaryKeyAttribute. Set the primaryKeyAttributeName and try again! %@", self);
- return nil;
- }
-
- NSFetchRequest *fetchRequest = [NSFetchRequest new];
- fetchRequest.entity = entity;
- fetchRequest.predicate = predicate;
- fetchRequest.fetchLimit = 1;
- __block NSError *error;
- __block NSArray *objects;
- [self performBlockAndWait:^{
- objects = [self executeFetchRequest:fetchRequest error:&error];
- }];
- if (! objects) {
- RKLogCoreDataError(error);
- return nil;
- }
-
- if ([objects count] == 1) {
- return [objects objectAtIndex:0];
- }
-
- return nil;
-}
-
-- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue
-{
- NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self];
- return [self fetchObjectForEntity:entity withValueForPrimaryKeyAttribute:primaryKeyValue];
-}
+//- (id)fetchObjectForEntity:(NSEntityDescription *)entity withValueForPrimaryKeyAttribute:(id)primaryKeyValue
+//{
+// NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
+// if (! predicate) {
+// RKLogWarning(@"Attempt to fetchObjectForEntity for entity with nil primaryKeyAttribute. Set the primaryKeyAttributeName and try again! %@", self);
+// return nil;
+// }
+//
+// NSFetchRequest *fetchRequest = [NSFetchRequest new];
+// fetchRequest.entity = entity;
+// fetchRequest.predicate = predicate;
+// fetchRequest.fetchLimit = 1;
+// __block NSError *error;
+// __block NSArray *objects;
+// [self performBlockAndWait:^{
+// objects = [self executeFetchRequest:fetchRequest error:&error];
+// }];
+// if (! objects) {
+// RKLogCoreDataError(error);
+// return nil;
+// }
+//
+// if ([objects count] == 1) {
+// return [objects objectAtIndex:0];
+// }
+//
+// return nil;
+//}
+//
+//- (id)fetchObjectForEntityForName:(NSString *)entityName withValueForPrimaryKeyAttribute:(id)primaryKeyValue
+//{
+// NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self];
+// return [self fetchObjectForEntity:entity withValueForPrimaryKeyAttribute:primaryKeyValue];
+//}
- (BOOL)saveToPersistentStore:(NSError **)error
{
View
26 Code/CoreData/RKConnectionDescription.h
@@ -0,0 +1,26 @@
+//
+// RKConnectionDescription.h
+// RestKit
+//
+// Created by Blake Watters on 11/20/12.
+// Copyright (c) 2012 RestKit. All rights reserved.
+//
+
+#import <CoreData/CoreData.h>
+
+/**
+ */
+@interface RKConnectionDescription : NSObject <NSCopying>
+
+@property (nonatomic, strong, readonly) NSRelationshipDescription *relationship;
+
+- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)sourceToDestinationEntityAttributes;
+- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath;
+
+@property (nonatomic, copy, readonly) NSDictionary *attributes; // nil unless foreign key
+- (BOOL)isForeignKeyConnection;
+
+@property (nonatomic, copy, readonly) NSString *keyPath; // nil unless keyPath description
+- (BOOL)isKeyPathConnection;
+
+@end
View
129 Code/CoreData/RKConnectionDescription.m
@@ -0,0 +1,129 @@
+//
+// RKConnectionDescription.m
+// RestKit
+//
+// Created by Blake Watters on 11/20/12.
+// Copyright (c) 2012 RestKit. All rights reserved.
+//
+
+#import "RKConnectionDescription.h"
+
+static NSSet *RKSetWithInvalidAttributesForEntity(NSArray *attributes, NSEntityDescription *entity)
+{
+ NSMutableSet *attributesSet = [NSMutableSet setWithArray:attributes];
+ NSSet *validAttributeNames = [NSSet setWithArray:[[entity attributesByName] allKeys]];
+ [attributesSet minusSet:validAttributeNames];
+ return attributesSet;
+}
+
+// Provides support for connecting a relationship by
+@interface RKForeignKeyConnectionDescription : RKConnectionDescription
+@end
+
+// Provides support for connecting a relationship by traversing the object graph
+@interface RKKeyPathConnectionDescription : RKConnectionDescription
+@end
+
+@interface RKConnectionDescription ()
+@property (nonatomic, strong, readwrite) NSRelationshipDescription *relationship;
+@property (nonatomic, copy, readwrite) NSDictionary *attributes;
+@property (nonatomic, copy, readwrite) NSString *keyPath;
+@end
+
+@implementation RKConnectionDescription
+
+- (id)initWithRelationship:(NSRelationshipDescription *)relationship attributes:(NSDictionary *)attributes
+{
+ NSParameterAssert(relationship);
+ NSParameterAssert(attributes);
+ NSAssert([attributes count], @"Cannot connect a relationship without at least one pair of attributes describing the connection");
+ NSSet *invalidSourceAttributes = RKSetWithInvalidAttributesForEntity([attributes allKeys], [relationship entity]);
+ NSAssert([invalidSourceAttributes count] == 0, @"Cannot connect relationship: invalid attributes given for source entity '%@': %@", [[relationship entity] name], [[invalidSourceAttributes allObjects] componentsJoinedByString:@", "]);
+ NSSet *invalidDestinationAttributes = RKSetWithInvalidAttributesForEntity([attributes allValues], [relationship destinationEntity]);
+ NSAssert([invalidDestinationAttributes count] == 0, @"Cannot connect relationship: invalid attributes given for destination entity '%@': %@", [[relationship destinationEntity] name], [[invalidDestinationAttributes allObjects] componentsJoinedByString:@", "]);
+
+ self = [[RKForeignKeyConnectionDescription alloc] init];
+ if (self) {
+ self.relationship = relationship;
+ self.attributes = attributes;
+ }
+ return self;
+}
+
+- (id)initWithRelationship:(NSRelationshipDescription *)relationship keyPath:(NSString *)keyPath
+{
+ NSParameterAssert(relationship);
+ NSParameterAssert(keyPath);
+ self = [[RKKeyPathConnectionDescription alloc] init];
+ if (self) {
+ self.relationship = relationship;
+ self.keyPath = keyPath;
+ }
+ return self;
+}
+
+- (id)init
+{
+ if ([self class] == [RKConnectionDescription class]) {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"%@ Failed to call designated initializer. "
+ "Invoke initWithRelationship:sourceKeyPath:destinationKeyPath:matcher: instead.",
+ NSStringFromClass([self class])]
+ userInfo:nil];
+ }
+ return [super init];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ if ([self isForeignKeyConnection]) {
+ return [[[self class] allocWithZone:zone] initWithRelationship:self.relationship attributes:self.attributes];
+ } else if ([self isKeyPathConnection]) {
+ return [[[self class] allocWithZone:zone] initWithRelationship:self.relationship keyPath:self.keyPath];
+ }
+
+ return nil;
+}
+
+- (BOOL)isForeignKeyConnection
+{
+ return NO;
+}
+
+- (BOOL)isKeyPathConnection
+{
+ return NO;
+}
+
+@end
+
+@implementation RKForeignKeyConnectionDescription
+
+- (BOOL)isForeignKeyConnection
+{
+ return YES;
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<%@:%p connecting Relationship '%@' from Entity '%@' to Destination Entity '%@' with attributes=%@>",
+ NSStringFromClass([self class]), self, [self.relationship name], [[self.relationship entity] name],
+ [[self.relationship destinationEntity] name], self.attributes];
+}
+
+@end
+
+@implementation RKKeyPathConnectionDescription
+
+- (BOOL)isKeyPathConnection
+{
+ return YES;
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<%@:%p connecting Relationship '%@' of Entity '%@' with keyPath=%@>",
+ NSStringFromClass([self class]), self, [self.relationship name], [[self.relationship entity] name], self.keyPath];
+}
+
+@end
View
8 Code/CoreData/RKRelationshipConnectionOperation.h → Code/CoreData/RKConnectionOperation.h
@@ -20,7 +20,7 @@
#import <Foundation/Foundation.h>
-@class RKConnectionMapping;
+@class RKConnectionDescription;
@protocol RKManagedObjectCaching;
/**
@@ -30,7 +30,7 @@
@see `RKConnectionMapping`
*/
-@interface RKRelationshipConnectionOperation : NSOperation
+@interface RKConnectionOperation : NSOperation
///-------------------------------------------------------
/// @name Initializing a Relationship Connection Operation
@@ -45,7 +45,7 @@
@return The receiver, initialized with the given managed object, connection mapping, and managed object cache.
*/
- (id)initWithManagedObject:(NSManagedObject *)managedObject
- connectionMapping:(RKConnectionMapping *)connectionMapping
+ connection:(RKConnectionDescription *)connection
managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
///--------------------------------------------
@@ -60,7 +60,7 @@
/**
The connection mapping describing the relationship connection the receiver will attempt to connect.
*/
-@property (nonatomic, strong, readonly) RKConnectionMapping *connectionMapping;
+@property (nonatomic, strong, readonly) RKConnectionDescription *connection;
/**
The managed object cache the receiver will use to fetch a related object satisfying the connection mapping.
View
130 Code/CoreData/RKRelationshipConnectionOperation.m → Code/CoreData/RKConnectionOperation.m
@@ -19,7 +19,8 @@
//
#import <CoreData/CoreData.h>
-#import "RKRelationshipConnectionOperation.h"
+#import "RKConnectionOperation.h"
+#import "RKConnectionDescription.h"
#import "RKEntityMapping.h"
#import "RKLog.h"
#import "RKManagedObjectCaching.h"
@@ -37,9 +38,21 @@ static id RKMutableSetValueForRelationship(NSRelationshipDescription *relationsh
return [relationship isOrdered] ? [NSMutableOrderedSet orderedSet] : [NSMutableSet set];
}
-@interface RKRelationshipConnectionOperation ()
+static NSDictionary *RKConnectionAttributeValuesWithObject(RKConnectionDescription *connection, NSManagedObject *managedObject)
+{
+ NSCAssert([connection isForeignKeyConnection], @"Only valid for a foreign key connection");
+ NSMutableDictionary *destinationEntityAttributeValues = [NSMutableDictionary dictionaryWithCapacity:[connection.attributes count]];
+ for (NSString *sourceAttribute in connection.attributes) {
+ NSString *destinationAttribute = connection.attributes[sourceAttribute];
+ id sourceValue = [managedObject valueForKey:sourceAttribute];
+ [destinationEntityAttributeValues setValue:sourceValue forKey:destinationAttribute];
+ }
+ return destinationEntityAttributeValues;
+}
+
+@interface RKConnectionOperation ()
@property (nonatomic, strong, readwrite) NSManagedObject *managedObject;
-@property (nonatomic, strong, readwrite) RKConnectionMapping *connectionMapping;
+@property (nonatomic, strong, readwrite) RKConnectionDescription *connection;
@property (nonatomic, strong, readwrite) id<RKManagedObjectCaching> managedObjectCache;
@property (nonatomic, strong, readwrite) NSError *error;
@property (nonatomic, strong, readwrite) id connectedValue;
@@ -49,19 +62,20 @@ @interface RKRelationshipConnectionOperation ()
@end
-@implementation RKRelationshipConnectionOperation
-
+@implementation RKConnectionOperation
-- (id)initWithManagedObject:(NSManagedObject *)managedObject connectionMapping:(RKConnectionMapping *)connectionMapping managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache
+- (id)initWithManagedObject:(NSManagedObject *)managedObject
+ connection:(RKConnectionDescription *)connection
+ managedObjectCache:(id<RKManagedObjectCaching>)managedObjectCache;
{
NSParameterAssert(managedObject);
NSAssert([managedObject isKindOfClass:[NSManagedObject class]], @"Relationship connection requires an instance of NSManagedObject");
- NSParameterAssert(connectionMapping);
+ NSParameterAssert(connection);
NSParameterAssert(managedObjectCache);
self = [self init];
if (self) {
self.managedObject = managedObject;
- self.connectionMapping = connectionMapping;
+ self.connection = connection;
self.managedObjectCache = managedObjectCache;
}
@@ -73,23 +87,14 @@ - (NSManagedObjectContext *)managedObjectContext
return self.managedObject.managedObjectContext;
}
-- (NSManagedObject *)findOneConnectedWithSourceValue:(id)sourceValue
-{
- NSAssert(self.managedObjectContext, @"Cannot lookup objects with a nil managedObjectContext");
- return [self.managedObjectCache findInstanceOfEntity:self.connectionMapping.relationship.destinationEntity
- withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
- value:sourceValue
- inManagedObjectContext:self.managedObjectContext];
-}
-
- (id)relationshipValueWithConnectionResult:(id)result
{
// TODO: Replace with use of object mapping engine for type conversion
// NOTE: This is a nasty hack to work around the fact that NSOrderedSet does not support key-value
// collection operators. We try to detect and unpack a doubly wrapped collection
- if ([self.connectionMapping.relationship isToMany] && RKObjectIsCollectionOfCollections(result)) {
- id mutableSet = RKMutableSetValueForRelationship(self.connectionMapping.relationship);
+ if ([self.connection.relationship isToMany] && RKObjectIsCollectionOfCollections(result)) {
+ id mutableSet = RKMutableSetValueForRelationship(self.connection.relationship);
for (id<NSFastEnumeration> enumerable in result) {
for (id object in enumerable) {
[mutableSet addObject:object];
@@ -99,27 +104,27 @@ - (id)relationshipValueWithConnectionResult:(id)result
return mutableSet;
}
- if ([self.connectionMapping.relationship isToMany]) {
+ if ([self.connection.relationship isToMany]) {
if ([result isKindOfClass:[NSArray class]]) {
- if ([self.connectionMapping.relationship isOrdered]) {
+ if ([self.connection.relationship isOrdered]) {
return [NSOrderedSet orderedSetWithArray:result];
} else {
return [NSSet setWithArray:result];
}
} else if ([result isKindOfClass:[NSSet class]]) {
- if ([self.connectionMapping.relationship isOrdered]) {
+ if ([self.connection.relationship isOrdered]) {
return [NSOrderedSet orderedSetWithSet:result];
} else {
return result;
}
} else if ([result isKindOfClass:[NSOrderedSet class]]) {
- if ([self.connectionMapping.relationship isOrdered]) {
+ if ([self.connection.relationship isOrdered]) {
return result;
} else {
return [(NSOrderedSet *)result set];
}
} else {
- if ([self.connectionMapping.relationship isOrdered]) {
+ if ([self.connection.relationship isOrdered]) {
return [NSOrderedSet orderedSetWithObject:result];
} else {
return [NSSet setWithObject:result];
@@ -143,61 +148,56 @@ - (NSMutableSet *)findAllConnectedWithSourceValue:(id)sourceValue
for (id value in values) {
NSAssert(self.managedObjectContext, @"Cannot lookup objects with a nil managedObjectContext");
- NSArray *objects = [self.managedObjectCache findInstancesOfEntity:self.connectionMapping.relationship.destinationEntity
- withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
- value:value
- inManagedObjectContext:self.managedObjectContext];
- [result addObjectsFromArray:objects];
+// NSArray *objects = [self.managedObjectCache findInstancesOfEntity:self.connectionMapping.relationship.destinationEntity
+// withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
+// value:value
+// inManagedObjectContext:self.managedObjectContext];
+// [result addObjectsFromArray:objects];
}
return result;
}
-- (BOOL)isToMany
-{
- return self.connectionMapping.relationship.isToMany;
-}
-
-- (BOOL)checkMatcher
-{
- if (!self.connectionMapping.matcher) {
- return YES;
- } else {
- return [self.connectionMapping.matcher matches:self.managedObject];
- }
-}
+//- (NSManagedObject *)findOneConnectedWithSourceValue:(id)sourceValue
+//{
+// NSAssert(self.managedObjectContext, @"Cannot lookup objects with a nil managedObjectContext");
+// NSArray *
+// return [self.managedObjectCache findInstanceOfEntity:self.connectionMapping.relationship.destinationEntity
+// withPrimaryKeyAttribute:self.connectionMapping.destinationKeyPath
+// value:sourceValue
+// inManagedObjectContext:self.managedObjectContext];
+//}
- (id)findConnected
{
- if ([self checkMatcher]) {
- id connectionResult = nil;
- if ([self.connectionMapping isForeignKeyConnection]) {
- BOOL isToMany = [self isToMany];
- id sourceValue = [self.managedObject valueForKey:self.connectionMapping.sourceKeyPath];
- if (isToMany) {
- connectionResult = [self findAllConnectedWithSourceValue:sourceValue];
- } else {
- connectionResult = [self findOneConnectedWithSourceValue:sourceValue];
- }
- } else if ([self.connectionMapping isKeyPathConnection]) {
- connectionResult = [self.managedObject valueForKeyPath:self.connectionMapping.sourceKeyPath];
+ id connectionResult = nil;
+ if ([self.connection isForeignKeyConnection]) {
+ NSDictionary *attributeValues = RKConnectionAttributeValuesWithObject(self.connection, self.managedObject);
+ NSArray *managedObjects = [self.managedObjectCache managedObjectsWithEntity:[self.connection.relationship destinationEntity]
+ attributeValues:attributeValues
+ inManagedObjectContext:self.managedObjectContext];
+ if ([self.connection.relationship isToMany]) {
+ connectionResult = managedObjects;
} else {
- @throw [NSException exceptionWithName:NSInternalInconsistencyException
- reason:[NSString stringWithFormat:@"%@ Attempted to establish a relationship using a mapping"
- "specifies neither a foreign key or a key path connection: %@",
- NSStringFromClass([self class]), self.connectionMapping]
- userInfo:nil];
+ if ([managedObjects count] > 1) RKLogWarning(@"Retrieved %d objects satisfying connection criteria for one-to-one relationship connection: only the first result will be connected.", [managedObjects count]);
+ connectionResult = managedObjects[0];
}
-
- return [self relationshipValueWithConnectionResult:connectionResult];
+ } else if ([self.connection isKeyPathConnection]) {
+ connectionResult = [self.managedObject valueForKeyPath:self.connection.keyPath];
} else {
- return nil;
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
+ reason:[NSString stringWithFormat:@"%@ Attempted to establish a relationship using a mapping that"
+ " specifies neither a foreign key or a key path connection: %@",
+ NSStringFromClass([self class]), self.connection]
+ userInfo:nil];
}
+
+ return [self relationshipValueWithConnectionResult:connectionResult];
}
- (void)connectRelationship
{
- NSString *relationshipName = self.connectionMapping.relationship.name;
- RKLogTrace(@"Connecting relationship '%@' with mapping: %@", relationshipName, self.connectionMapping);
+ NSString *relationshipName = self.connection.relationship.name;
+ RKLogTrace(@"Connecting relationship '%@' with mapping: %@", relationshipName, self.connection);
[self.managedObjectContext performBlockAndWait:^{
self.connectedValue = [self findConnected];
[self.managedObject setValue:self.connectedValue forKeyPath:relationshipName];
@@ -214,7 +214,7 @@ - (void)main
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@:%p %@ in %@ using %@>",
- [self class], self, self.connectionMapping, self.managedObjectContext, self.managedObjectCache];
+ [self class], self, self.connection, self.managedObjectContext, self.managedObjectCache];
}
@end
View
29 Code/CoreData/RKEntityByAttributeCache.h
@@ -41,12 +41,12 @@
Initializes the receiver with a given entity, attribute, and managed object context.
@param entity The Core Data entity description for the managed objects being cached.
- @param attributeName The name of an attribute within the cached entity that acts as the cache key.
+ @param attributeNames An array of attribute names used as the cache keys.
@param context The managed object context the cache retrieves the cached objects from.
@return The receiver, initialized with the given entity, attribute, and managed object
context.
*/
-- (id)initWithEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName managedObjectContext:(NSManagedObjectContext *)context;
+- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames managedObjectContext:(NSManagedObjectContext *)context;
///-----------------------------
/// @name Getting Cache Identity
@@ -58,9 +58,9 @@
@property (nonatomic, readonly) NSEntityDescription *entity;
/**
- An attribute that is part of the cached entity that acts as the cache key.
+ An array of attribute names specifying attributes of the cached entity that act as the cache key.
*/
-@property (nonatomic, readonly) NSString *attribute;
+@property (nonatomic, readonly) NSArray *attributes;
/**
The managed object context the receiver fetches cached objects from.
@@ -105,14 +105,12 @@
- (NSUInteger)count;
/**
- Returns the total number of cached objects with a given value for the attribute acting as the cache key.
+ Returns the total number of cached objects whose attributes match the values in the given dictionary of attribute values.
- @param attributeValue The value for the cache key attribute to retrieve
- a count of the objects with a matching value.
- @return The number of objects in the cache with the given value for the cache
- attribute of the receiver.
+ @param attributeValues The value for the cache key attribute to retrieve a count of the objects with a matching value.
+ @return The number of objects in the cache with the given value for the cache attribute of the receiver.
*/
-- (NSUInteger)countWithAttributeValue:(id)attributeValue;
+- (NSUInteger)countWithAttributeValues:(NSDictionary *)attributeValues;
/**
Returns the number of unique attribute values contained within the receiver.
@@ -137,17 +135,16 @@
@param attributeValue The value with which to check the cache for objects with a matching value.
@return YES if one or more objects with the given value for the cache key attribute is present in the cache, otherwise NO.
*/
-- (BOOL)containsObjectWithAttributeValue:(id)attributeValue;
+- (BOOL)containsObjectWithAttributeValues:(NSDictionary *)attributeValues;
/**
- Returns the first object with a matching value for the cache key attribute
- in a given managed object context.
+ Returns the first object with a matching value for the cache key attributes in a given managed object context.
- @param attributeValue A value for the cache key attribute.
+ @param attributeValues A value for the cache key attribute.
@param context The managed object context to retrieve the object from.
@return An object with the value of attribute matching attributeValue or nil.
*/
-- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context;
+- (NSManagedObject *)objectWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
/**
Returns the collection of objects with a matching value for the cache key attribute in a given managed object context.
@@ -156,7 +153,7 @@
@param context The managed object context to retrieve the objects from.
@return An array of objects with the value of attribute matching attributeValue or an empty array.
*/
-- (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context;
+- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
///------------------------------
/// @name Managing Cached Objects
View
139 Code/CoreData/RKEntityByAttributeCache.m
@@ -32,23 +32,44 @@
#undef RKLogComponent
#define RKLogComponent RKlcl_cRestKitCoreDataCache
+static id RKCacheKeyValueForEntityAttributeWithValue(NSEntityDescription *entity, NSString *attribute, id value)
+{
+ if ([value isKindOfClass:[NSString class]] || [value isEqual:[NSNull null]]) {
+ return value;
+ }
+
+ Class attributeType = [[RKPropertyInspector sharedInspector] classForPropertyNamed:attribute ofEntity:entity];
+ return [attributeType instancesRespondToSelector:@selector(stringValue)] ? [value stringValue] : value;
+}
+
+static NSString *RKCacheKeyForEntityWithAttributeValues(NSEntityDescription *entity, NSDictionary *attributeValues)
+{
+ NSArray *sortedAttributes = [[attributeValues allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
+ NSMutableArray *sortedValues = [NSMutableArray arrayWithCapacity:[sortedAttributes count]];
+ [sortedAttributes enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
+ id cacheKeyValue = RKCacheKeyValueForEntityAttributeWithValue(entity, attributeName, attributeValues[attributeName]);
+ [sortedValues addObject:cacheKeyValue];
+ }];
+
+ return [sortedValues componentsJoinedByString:@":"];
+}
+
@interface RKEntityByAttributeCache ()
-@property (nonatomic, strong) NSMutableDictionary *attributeValuesToObjectIDs;
+@property (nonatomic, strong) NSMutableDictionary *cacheKeysToObjectIDs;
@end
@implementation RKEntityByAttributeCache
-
-- (id)initWithEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName managedObjectContext:(NSManagedObjectContext *)context
+- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames managedObjectContext:(NSManagedObjectContext *)context
{
NSParameterAssert(entity);
- NSParameterAssert(attributeName);
+ NSParameterAssert(attributeNames);
NSParameterAssert(context);
self = [self init];
if (self) {
_entity = entity;
- _attribute = attributeName;
+ _attributes = attributeNames;
_managedObjectContext = context;
_monitorsContextForChanges = YES;
@@ -75,35 +96,25 @@ - (void)dealloc
- (NSUInteger)count
{
- return [[[self.attributeValuesToObjectIDs allValues] valueForKeyPath:@"@sum.@count"] integerValue];
+ return [[[self.cacheKeysToObjectIDs allValues] valueForKeyPath:@"@sum.@count"] integerValue];
}
- (NSUInteger)countOfAttributeValues
{
- return [self.attributeValuesToObjectIDs count];
+ return [self.cacheKeysToObjectIDs count];
}
-- (NSUInteger)countWithAttributeValue:(id)attributeValue
+- (NSUInteger)countWithAttributeValues:(NSDictionary *)attributeValues
{
- return [[self objectsWithAttributeValue:attributeValue inContext:self.managedObjectContext] count];
-}
-
-- (BOOL)shouldCoerceAttributeToString:(NSString *)attributeValue
-{
- if ([attributeValue isKindOfClass:[NSString class]] || [attributeValue isEqual:[NSNull null]]) {
- return NO;
- }
-
- Class attributeType = [[RKPropertyInspector sharedInspector] classForPropertyNamed:self.attribute ofEntity:self.entity];
- return [attributeType instancesRespondToSelector:@selector(stringValue)];
+ return [[self objectsWithAttributeValues:attributeValues inContext:self.managedObjectContext] count];
}
- (void)load
{
- RKLogDebug(@"Loading entity cache for Entity '%@' by attribute '%@' in managed object context %@ (concurrencyType = %ld)",
- self.entity.name, self.attribute, self.managedObjectContext, (unsigned long)self.managedObjectContext.concurrencyType);
- @synchronized(self.attributeValuesToObjectIDs) {
- self.attributeValuesToObjectIDs = [NSMutableDictionary dictionary];
+ RKLogDebug(@"Loading entity cache for Entity '%@' by attributes '%@' in managed object context %@ (concurrencyType = %ld)",
+ self.entity.name, self.attributes, self.managedObjectContext, (unsigned long)self.managedObjectContext.concurrencyType);
+ @synchronized(self.cacheKeysToObjectIDs) {
+ self.cacheKeysToObjectIDs = [NSMutableDictionary dictionary];
NSExpressionDescription* objectIDExpression = [NSExpressionDescription new];
objectIDExpression.name = @"objectID";
@@ -114,7 +125,7 @@ - (void)load
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = self.entity;
fetchRequest.resultType = NSDictionaryResultType;
- fetchRequest.propertiesToFetch = [NSArray arrayWithObjects:objectIDExpression, self.attribute, nil];
+ fetchRequest.propertiesToFetch = [self.attributes arrayByAddingObject:objectIDExpression];
[self.managedObjectContext performBlockAndWait:^{
NSError *error = nil;
@@ -127,9 +138,9 @@ - (void)load
}
for (NSDictionary *dictionary in dictionaries) {
- id attributeValue = [dictionary objectForKey:self.attribute];
NSManagedObjectID *objectID = [dictionary objectForKey:@"objectID"];
- [self setObjectID:objectID forAttributeValue:attributeValue];
+ NSDictionary *attributeValues = [dictionary dictionaryWithValuesForKeys:self.attributes];
+ [self setObjectID:objectID forAttributeValues:attributeValues];
}
}];
}
@@ -137,9 +148,9 @@ - (void)load
- (void)flush
{
- @synchronized(self.attributeValuesToObjectIDs) {
- RKLogDebug(@"Flushing entity cache for Entity '%@' by attribute '%@'", self.entity.name, self.attribute);
- self.attributeValuesToObjectIDs = nil;
+ @synchronized(self.cacheKeysToObjectIDs) {
+ RKLogDebug(@"Flushing entity cache for Entity '%@' by attributes '%@'", self.entity.name, self.attributes);
+ self.cacheKeysToObjectIDs = nil;
}
}
@@ -151,7 +162,7 @@ - (void)reload
- (BOOL)isLoaded
{
- return (self.attributeValuesToObjectIDs != nil);
+ return (self.cacheKeysToObjectIDs != nil);
}
- (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)context
@@ -180,18 +191,19 @@ - (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID inContext:(
return object;
}
-- (NSManagedObject *)objectWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context
+- (NSManagedObject *)objectWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
- NSArray *objects = [self objectsWithAttributeValue:attributeValue inContext:context];
+ NSArray *objects = [self objectsWithAttributeValues:attributeValues inContext:context];
return ([objects count] > 0) ? [objects objectAtIndex:0] : nil;
}
-- (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedObjectContext *)context
+- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
- attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
+ // TODO: Assert that the attribute values contains all of the cache attributes!!!
+ NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
NSArray *objectIDs = nil;
- @synchronized(self.attributeValuesToObjectIDs) {
- objectIDs = [[NSArray alloc] initWithArray:[self.attributeValuesToObjectIDs objectForKey:attributeValue] copyItems:YES];
+ @synchronized(self.cacheKeysToObjectIDs) {
+ objectIDs = [[NSArray alloc] initWithArray:[self.cacheKeysToObjectIDs objectForKey:cacheKey] copyItems:YES];
}
if ([objectIDs count]) {
/**
@@ -205,8 +217,8 @@ - (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedOb
if (object) {
[objects addObject:object];
} else {
- RKLogDebug(@"Evicting objectID association for attribute '%@'=>'%@' of Entity '%@': %@", self.attribute, attributeValue, self.entity.name, objectID);
- [self removeObjectID:objectID forAttributeValue:attributeValue];
+ RKLogDebug(@"Evicting objectID association for attributes %@ of Entity '%@': %@", attributeValues, self.entity.name, objectID);
+ [self removeObjectID:objectID forAttributeValues:attributeValues];
}
}
@@ -216,12 +228,12 @@ - (NSArray *)objectsWithAttributeValue:(id)attributeValue inContext:(NSManagedOb
return [NSArray array];
}
-- (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValue:(id)attributeValue
+- (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValues:(NSDictionary *)attributeValues
{
- @synchronized(self.attributeValuesToObjectIDs) {
- attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
- if (attributeValue) {
- NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
+ @synchronized(self.cacheKeysToObjectIDs) {
+ if (attributeValues && [attributeValues count]) {
+ NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
+ NSMutableArray *objectIDs = [self.cacheKeysToObjectIDs objectForKey:cacheKey];
if (objectIDs) {
if (! [objectIDs containsObject:objectID]) {
[objectIDs addObject:objectID];
@@ -230,27 +242,25 @@ - (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValue:(id)attribut
objectIDs = [NSMutableArray arrayWithObject:objectID];
}
-
- if (nil == self.attributeValuesToObjectIDs) self.attributeValuesToObjectIDs = [NSMutableDictionary dictionary];
- [self.attributeValuesToObjectIDs setValue:objectIDs forKey:attributeValue];
+ if (nil == self.cacheKeysToObjectIDs) self.cacheKeysToObjectIDs = [NSMutableDictionary dictionary];
+ [self.cacheKeysToObjectIDs setValue:objectIDs forKey:cacheKey];
} else {
- RKLogWarning(@"Unable to add object for object ID %@: nil value for attribute '%@'", objectID, self.attribute);
+ RKLogWarning(@"Unable to add object for object ID %@: empty values dictionary for attributes '%@'", objectID, self.attributes);
}
}
}
-- (void)removeObjectID:(NSManagedObjectID *)objectID forAttributeValue:(id)attributeValue
+- (void)removeObjectID:(NSManagedObjectID *)objectID forAttributeValues:(NSDictionary *)attributeValues
{
- @synchronized(self.attributeValuesToObjectIDs) {
- // Coerce to a string if possible
- attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
- if (attributeValue) {
- NSMutableArray *objectIDs = [self.attributeValuesToObjectIDs objectForKey:attributeValue];
+ @synchronized(self.cacheKeysToObjectIDs) {
+ if (attributeValues && [attributeValues count]) {
+ NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues);
+ NSMutableArray *objectIDs = [self.cacheKeysToObjectIDs objectForKey:cacheKey];
if (objectIDs && [objectIDs containsObject:objectID]) {
[objectIDs removeObject:objectID];
}
} else {
- RKLogWarning(@"Unable to remove object for object ID %@: nil value for attribute '%@'", objectID, self.attribute);
+ RKLogWarning(@"Unable to remove object for object ID %@: empty values dictionary for attributes '%@'", objectID, self.attributes);
}
}
}
@@ -258,42 +268,39 @@ - (void)removeObjectID:(NSManagedObjectID *)objectID forAttributeValue:(id)attri
- (void)addObject:(NSManagedObject *)object
{
__block NSEntityDescription *entity;
- __block id attributeValue;
+ __block NSDictionary *attributeValues;
__block NSManagedObjectID *objectID;
[self.managedObjectContext performBlockAndWait:^{
entity = object.entity;
objectID = [object objectID];
- attributeValue = [object valueForKey:self.attribute];
+ attributeValues = [object dictionaryWithValuesForKeys:self.attributes];
}];
NSAssert([entity isKindOfEntity:self.entity], @"Cannot add object with entity '%@' to cache for entity of '%@'", [entity name], [self.entity name]);
- // Coerce to a string if possible
- [self setObjectID:objectID forAttributeValue:attributeValue];
+ [self setObjectID:objectID forAttributeValues:attributeValues];
}
- (void)removeObject:(NSManagedObject *)object
{
__block NSEntityDescription *entity;
- __block id attributeValue;
+ __block NSDictionary *attributeValues;
__block NSManagedObjectID *objectID;
[object.managedObjectContext performBlockAndWait:^{
entity = object.entity;
objectID = [object objectID];
- attributeValue = [object valueForKey:self.attribute];
+ attributeValues = [object dictionaryWithValuesForKeys:self.attributes];
}];
NSAssert([entity isKindOfEntity:self.entity], @"Cannot remove object with entity '%@' from cache for entity of '%@'", [entity name], [self.entity name]);
- [self removeObjectID:objectID forAttributeValue:attributeValue];
+ [self removeObjectID:objectID forAttributeValues:attributeValues];
}
-- (BOOL)containsObjectWithAttributeValue:(id)attributeValue
+- (BOOL)containsObjectWithAttributeValues:(NSDictionary *)attributeValues
{
- // Coerce to a string if possible
- attributeValue = [self shouldCoerceAttributeToString:attributeValue] ? [attributeValue stringValue] : attributeValue;
- return [[self objectsWithAttributeValue:attributeValue inContext:self.managedObjectContext] count] > 0;
+ return [[self objectsWithAttributeValues:attributeValues inContext:self.managedObjectContext] count] > 0;
}
- (BOOL)containsObject:(NSManagedObject *)object
{
- NSArray *allObjectIDs = [[self.attributeValuesToObjectIDs allValues] valueForKeyPath:@"@distinctUnionOfArrays.self"];
+ NSArray *allObjectIDs = [[self.cacheKeysToObjectIDs allValues] valueForKeyPath:@"@distinctUnionOfArrays.self"];
return [allObjectIDs containsObject:object.objectID];
}
View
26 Code/CoreData/RKEntityCache.h
@@ -50,50 +50,48 @@
*/
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
-///-----------------------------------------------------------------------------
-/// @name Caching Objects by Attribute
-///-----------------------------------------------------------------------------
+///------------------------------------
+/// @name Caching Objects by Attributes
+///------------------------------------
/**
Caches all instances of an entity using the value for an attribute as the cache key.
@param entity The entity to cache all instances of.
- @param attributeName The attribute to cache the instances by.
+ @param attributeNames The attributes to cache the instances by.
*/
-- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName;
+- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames;
/**
Returns a Boolean value indicating if all instances of an entity have been cached by a given attribute name.
@param entity The entity to check the cache status of.
- @param attributeName The attribute to check the cache status with.
+ @param attributeNames The attributes to check the cache status with.
@return YES if the cache has been loaded with instances with the given attribute, else NO.
*/
-- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttribute:(NSString *)attributeName;
+- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttributes:(NSArray *)attributeNames;
/**
Retrieves the first cached instance of a given entity where the specified attribute matches the given value.
@param entity The entity to search the cache for instances of.
- @param attributeName The attribute to search the cache for matches with.
- @param attributeValue The value of the attribute to return a match for.
+ @param attributeValues The attribute values return a match for.
@param context The managed object from which to retrieve the cached results.
@return A matching managed object instance or nil.
@raise NSInvalidArgumentException Raised if instances of the entity and attribute have not been cached.
*/
-- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context;
+- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
/**
Retrieves all cached instances of a given entity where the specified attribute matches the given value.
@param entity The entity to search the cache for instances of.
- @param attributeName The attribute to search the cache for matches with.
- @param attributeValue The value of the attribute to return a match for.
+ @param attributeValues The attribute values return a match for.
@param context The managed object from which to retrieve the cached results.
@return All matching managed object instances or nil.
@raise NSInvalidArgumentException Raised if instances of the entity and attribute have not been cached.
*/
-- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context;
+- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context;
///-----------------------------------------------------------------------------
// @name Accessing Underlying Caches
@@ -106,7 +104,7 @@
@param attributeName The attribute to retrieve the entity attribute cache object for.
@return The entity attribute cache for the given entity and attribute, or nil if none was found.
*/
-- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName;
+- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames;
/**
Retrieves all entity attributes caches for a given entity.
View
48 Code/CoreData/RKEntityCache.m
@@ -46,58 +46,60 @@ - (id)init
}
-- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttribute:(NSString *)attributeName
+- (void)cacheObjectsForEntity:(NSEntityDescription *)entity byAttributes:(NSArray *)attributeNames
{
- NSAssert(entity, @"Cannot cache objects for a nil entity");
- NSAssert(attributeName, @"Cannot cache objects without an attribute");
- RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
+ NSParameterAssert(entity);
+ NSParameterAssert(attributeNames);
+ RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:attributeNames];
if (attributeCache && !attributeCache.isLoaded) {
[attributeCache load];
} else {
- attributeCache = [[RKEntityByAttributeCache alloc] initWithEntity:entity attribute:attributeName managedObjectContext:self.managedObjectContext];
+ attributeCache = [[RKEntityByAttributeCache alloc] initWithEntity:entity attributes:attributeNames managedObjectContext:self.managedObjectContext];
[attributeCache load];
[self.attributeCaches addObject:attributeCache];
}
}
-- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttribute:(NSString *)attributeName
+- (BOOL)isEntity:(NSEntityDescription *)entity cachedByAttributes:(NSArray *)attributeNames
{
- NSAssert(entity, @"Cannot check cache status for a nil entity");
- NSAssert(attributeName, @"Cannot check cache status for a nil attribute");
- RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
+ NSParameterAssert(entity);
+ NSParameterAssert(attributeNames);
+ RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:attributeNames];
return (attributeCache && attributeCache.isLoaded);
}
-- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context
+- (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
- NSAssert(entity, @"Cannot retrieve cached objects with a nil entity");
- NSAssert(attributeName, @"Cannot retrieve cached objects by a nil entity");
- RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
+ NSParameterAssert(entity);
+ NSParameterAssert(attributeValues);
+ NSParameterAssert(context);
+ RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:[attributeValues allKeys]];
if (attributeCache) {
- return [attributeCache objectWithAttributeValue:attributeValue inContext:context];
+ return [attributeCache objectWithAttributeValues:attributeValues inContext:context];
}
return nil;
}
-- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttribute:(NSString *)attributeName value:(id)attributeValue inContext:(NSManagedObjectContext *)context
+- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context
{
- NSAssert(entity, @"Cannot retrieve cached objects with a nil entity");
- NSAssert(attributeName, @"Cannot retrieve cached objects by a nil entity");
- RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attribute:attributeName];
+ NSParameterAssert(entity);
+ NSParameterAssert(attributeValues);
+ NSParameterAssert(context);
+ RKEntityByAttributeCache *attributeCache = [self attributeCacheForEntity:entity attributes:[attributeValues allKeys]];
if (attributeCache) {
- return [attributeCache objectsWithAttributeValue:attributeValue inContext:context];
+ return [attributeCache objectsWithAttributeValues:attributeValues inContext:context];
}
return [NSSet set];
}
-- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attribute:(NSString *)attributeName
+- (RKEntityByAttributeCache *)attributeCacheForEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributeNames
{
- NSAssert(entity, @"Cannot retrieve attribute cache for a nil entity");
- NSAssert(attributeName, @"Cannot retrieve attribute cache for a nil attribute");
+ NSParameterAssert(entity);
+ NSParameterAssert(attributeNames);
for (RKEntityByAttributeCache *cache in self.attributeCaches) {
- if ([cache.entity isEqual:entity] && [cache.attribute isEqualToString:attributeName]) {
+ if ([cache.entity isEqual:entity] && [cache.attributes isEqualToArray:attributeNames]) {
return cache;
}
}
View
39 Code/CoreData/RKEntityIdentifier.h
@@ -0,0 +1,39 @@
+//
+// RKEntityIdentifier.h
+// RestKit
+//
+// Created by Blake Watters on 11/20/12.
+// Copyright (c) 2012 RestKit. All rights reserved.
+//
+
+#import <CoreData/CoreData.h>
+
+extern NSString * const RKEntityIdentifierUserInfoKey;
+
+@class RKManagedObjectStore;
+
+// RKEntityIdentifier | RKManagedObjectIdentifier | RKEntityIdentity | RKResourceIdentity | RKEntityKey | RKIdentifier
+@interface RKEntityIdentifier : NSObject
+
+@property (nonatomic, strong, readonly) NSEntityDescription *entity;
+@property (nonatomic, copy, readonly) NSArray *attributes;
+
+// Convenience method
+// identifierWithEntityName:???
+// entityIdentifierWithName:
++ (id)identifierWithEntityName:(NSString *)entityName attributes:(NSArray *)attributes inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore;
+
+// Designated initializer
+- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributes;
+
+// Optional predicate for filtering matches
+@property (nonatomic, copy) NSPredicate *predicate;
+
+///-------------------------------------------
+/// @name Inferring Identifiers from the Model
+///-------------------------------------------
+
+// NOTE: Add not about checking the entity's userInfo
++ (RKEntityIdentifier *)inferredIdentifierForEntity:(NSEntityDescription *)entity;
+
+@end
View
117 Code/CoreData/RKEntityIdentifier.m
@@ -0,0 +1,117 @@
+//
+// RKEntityIdentifier.m
+// RestKit
+//
+// Created by Blake Watters on 11/20/12.
+// Copyright (c) 2012 RestKit. All rights reserved.
+//
+
+#import "RKEntityIdentifier.h"
+#import "RKManagedObjectStore.h"
+
+NSString * const RKEntityIdentifierUserInfoKey = @"RKEntityIdentifierAttributes";
+
+static NSArray *RKEntityIdentifierAttributesFromUserInfoOfEntity(NSEntityDescription *entity)
+{
+ id userInfoValue = [entity userInfo][RKEntityIdentifierUserInfoKey];
+ if (userInfoValue) {
+ NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
+ NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
+ [attributeNames enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
+ if (! [attributeName isKindOfClass:[NSString class]]) {
+ [NSException raise:NSInvalidArgumentException format:@"Invalid value given in user info key '%@' of entity '%@': expected an `NSString` or `NSArray` of strings, instead got '%@' (%@)", RKEntityIdentifierUserInfoKey, [entity name], attributeName, [attributeName class]];
+ }
+
+ NSAttributeDescription *attribute = [entity attributesByName][attributeName];
+ if (! attribute) {
+ [NSException raise:NSInvalidArgumentException format:@"Invalid identifier attribute specified in user info key '%@' of entity '%@': no attribue was found with the name '%@'", RKEntityIdentifierUserInfoKey, [entity name], attributeName];
+ }
+
+ [attributes addObject:attribute];
+ }];
+ return attributes;
+ }
+
+ return nil;
+}
+
+// Given 'Human', returns 'humanID'; Given 'AmenityReview' returns 'amenityReviewID'
+static NSString *RKEntityIdentifierAttributeNameForEntity(NSEntityDescription *entity)
+{
+ NSString *entityName = [entity name];
+ NSString *lowerCasedFirstCharacter = [[entityName substringToIndex:1] lowercaseString];
+ return [NSString stringWithFormat:@"%@%@ID", lowerCasedFirstCharacter, [entityName substringFromIndex:1]];
+}
+
+static NSArray *RKEntityIdentifierAttributeNames()
+{
+ return [NSArray arrayWithObjects:@"identifier", @"ID", @"URL", @"url", nil];
+}
+
+static NSArray *RKArrayOfAttributesForEntityFromAttributesOrNames(NSEntityDescription *entity, NSArray *attributesOrNames)
+{
+ NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributesOrNames count]];
+ for (id attributeOrName in attributesOrNames) {
+ if ([attributeOrName isKindOfClass:[NSAttributeDescription class]]) {
+ if (! [[entity properties] containsObject:attributeOrName]) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute value '%@' given for entity identifer: not found in the '%@' entity", attributeOrName, [entity name]];
+ [attributes addObject:attributeOrName];
+ } else if ([attributeOrName isKindOfClass:[NSString class]]) {
+ NSAttributeDescription *attribute = [entity attributesByName][attributeOrName];
+ if (!attribute) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute '%@': no attribute was found for the given name in the '%@' entity.", attributeOrName, [entity name]];
+ [attributes addObject:attribute];
+ } else {
+ [NSException raise:NSInvalidArgumentException format:@"Invalid value provided for entity identifier attribute: Acceptable values are either `NSAttributeDescription` or `NSString` objects."];
+ }
+ }
+
+ return attributes;
+}
+
+@interface RKEntityIdentifier ()
+@property (nonatomic, strong, readwrite) NSEntityDescription *entity;
+@property (nonatomic, copy, readwrite) NSArray *attributes;
+@end
+
+@implementation RKEntityIdentifier
+
++ (id)identifierWithEntityName:(NSString *)entityName attributes:(NSArray *)attributes inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore
+{
+ NSEntityDescription *entity = [managedObjectStore.managedObjectModel entitiesByName][entityName];
+ return [[self alloc] initWithEntity:entity attributes:attributes];
+}
+
+- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributes
+{
+ NSParameterAssert(entity);
+ NSParameterAssert(attributes);
+ NSAssert([attributes count], @"At least one attribute must be provided to identify managed objects");
+ self = [self init];
+ if (self) {
+ self.entity = entity;
+ self.attributes = RKArrayOfAttributesForEntityFromAttributesOrNames(entity, attributes);
+ }
+
+ return self;
+}
+
+#pragma mark - Idetifier Inference
+
++ (RKEntityIdentifier *)inferredIdentifierForEntity:(NSEntityDescription *)entity
+{
+ NSArray *attributes = RKEntityIdentifierAttributesFromUserInfoOfEntity(entity);
+ if (attributes) {
+ return [[RKEntityIdentifier alloc] initWithEntity:entity attributes:attributes];
+ }
+
+ NSMutableArray *identifyingAttributes = [NSMutableArray arrayWithObject:RKEntityIdentifierAttributeNameForEntity(entity)];
+ [identifyingAttributes addObjectsFromArray:RKEntityIdentifierAttributeNames()];
+ for (NSString *attributeName in identifyingAttributes) {
+ NSAttributeDescription *attribute = [entity attributesByName][attributeName];
+ if (attribute) {
+ return [[RKEntityIdentifier alloc] initWithEntity:entity attributes:@[ attribute ]];
+ }
+ }
+ return nil;
+}
+
+@end
View
94 Code/CoreData/RKEntityMapping.h
@@ -20,13 +20,26 @@
#import <CoreData/CoreData.h>
#import "RKObjectMapping.h"
-#import "RKConnectionMapping.h"
+#import "RKConnectionDescription.h"
#import "RKMacros.h"
+#import "RKEntityIdentifier.h"
@class RKManagedObjectStore;
/**
RKEntityMapping objects model an object mapping with a Core Data destination entity.
+
+ ## Entity Identification
+
+ TBD
+
+ ### Inferring Entity Identifiers
+
+ TBD
+
+ ## Connecting Relationships
+
+ TBD
*/
@interface RKEntityMapping : RKObjectMapping
@@ -62,58 +75,63 @@
*/
@property (nonatomic, strong) NSEntityDescription *entity;
-/**
- The name of the attribute on the destination entity that acts as the primary key for instances
- of the entity in the remote backend system. Used to uniquely identify objects within the store
- so that existing objects are updated rather than creating new ones.
-
- @warning Note that primaryKeyAttribute defaults to the primaryKeyAttribute configured
- on the NSEntityDescription for the entity targetted by the receiving mapping. This provides
- flexibility in cases where a single entity is the target of many mappings with differing
- primary key definitions.
+// The index for finding existing objects. Can be compound.
+@property (nonatomic, copy) RKEntityIdentifier *entityIdentifier;
- If the `primaryKeyAttribute` is set on an `RKEntityMapping` that targets an entity with a
- nil primaryKeyAttribute, then the primaryKeyAttribute will be set on the entity as well for
- convenience and backwards compatibility. This may change in the future.
+// Setting an existing entity identifier replaces it...
+- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier forRelationship:(NSString *)relationshipName;
- @see `[NSEntityDescription primaryKeyAttribute]`
- */
-// TODO: Make me readonly
-@property (nonatomic, strong) NSString *primaryKeyAttribute;
+// NOTE: This returns explicitly set value, then the entityIdentifier for the relationship mapping
+- (RKEntityIdentifier *)entityIdentifierForRelationship:(NSString *)relationshipName;
-/**
- Retrieves an array of RKConnectionMapping objects for connecting the receiver's relationships
- by primary key.
+///**
+// The name of the attribute on the destination entity that acts as the primary key for instances
+// of the entity in the remote backend system. Used to uniquely identify objects within the store
+// so that existing objects are updated rather than creating new ones.
+//
+// @warning Note that primaryKeyAttribute defaults to the primaryKeyAttribute configured
+// on the NSEntityDescription for the entity targetted by the receiving mapping. This provides
+// flexibility in cases where a single entity is the target of many mappings with differing
+// primary key definitions.
+//
+// If the `primaryKeyAttribute` is set on an `RKEntityMapping` that targets an entity with a
+// nil primaryKeyAttribute, then the primaryKeyAttribute will be set on the entity as well for
+// convenience and backwards compatibility. This may change in the future.
+//
+// @see `[NSEntityDescription primaryKeyAttribute]`
+// */
+//// TODO: Make me readonly
+//@property (nonatomic, strong) NSString *primaryKeyAttribute;
- @see `RKConnectionMapping`
- */
-@property (weak, nonatomic, readonly) NSArray *connectionMappings;
+@property (weak, nonatomic, readonly) NSArray *connections;
/**
Adds a connection mapping to the receiver.
@param connectionMapping The connection mapping to be added.
*/
-- (void)addConnectionMapping:(RKConnectionMapping *)connectionMapping;
-- (void)addConnectionMappingsFromArray:(NSArray *)arrayOfConnectionMappings;
+- (void)addConnection:(RKConnectionDescription *)connection;
+- (void)removeConnection:(RKConnectionDescription *)connection;
+- (void)addConnectionForRelationship:(id)relationshipOrName connectedBy:(id)connectionSpecifier;
+- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName;
-// Convenience method.
-- (RKConnectionMapping *)addConnectionMappingForRelationshipForName:(NSString *)relationshipName
- fromSourceKeyPath:(NSString *)sourceKeyPath
- toKeyPath:(NSString *)destinationKeyPath
- matcher:(RKDynamicMappingMatcher *)matcher;
-
-/**
- Removes a connection mapping from the receiver.
-
- @param connectionMapping The connection mapping to be added.
- */
-- (void)removeConnectionMapping:(RKConnectionMapping *)connectionMapping;
+///------------------------------------------
+/// @name Retrieving Default Attribute Values
+///------------------------------------------
/**
Returns the default value for the specified attribute as expressed in the Core Data entity definition. This value will
be assigned if the object mapping is applied and a value for a missing attribute is not present in the payload.
*/
-- (id)defaultValueForMissingAttribute:(NSString *)attributeName;
+- (id)defaultValueForAttribute:(NSString *)attributeName;
+
+///----------------------------------------------
+/// @name Configuring Entity Identifier Inference
+///----------------------------------------------
+
+// setInfersEntityIdentifiers:(BOOL) | setShouldInferEntityIdentifiers:
+// setEntityIdentificationInferenceEnabled: | :
++ (void)setEntityIdentifierInferenceEnabled:(BOOL)enabled; // Default YES
++ (BOOL)isEntityIdentifierInferenceEnabled;
@end
View
142 Code/CoreData/RKEntityMapping.m
@@ -22,7 +22,6 @@
#import "RKManagedObjectStore.h"
#import "RKDynamicMappingMatcher.h"
#import "RKPropertyInspector+CoreData.h"
-#import "NSEntityDescription+RKAdditions.h"
#import "RKLog.h"
#import "RKRelationshipMapping.h"
#import "RKObjectUtilities.h"
@@ -31,14 +30,33 @@
#undef RKLogComponent
#define RKLogComponent RKlcl_cRestKitCoreData
+static BOOL entityIdentifierInferenceEnabled = YES;
+
+static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
+{
+ if (! [RKEntityMapping isEntityIdentifierInferenceEnabled]) return;
+
+ entityMapping.entityIdentifier = [RKEntityIdentifier inferredIdentifierForEntity:entityMapping.entity];
+ [[entityMapping.entity relationshipsByName] enumerateKeysAndObjectsUsingBlock:^(NSString *relationshipName, NSRelationshipDescription *relationship, BOOL *stop) {
+ RKEntityIdentifier *entityIdentififer = [RKEntityIdentifier inferredIdentifierForEntity:relationship.destinationEntity];
+ if (entityIdentififer) {
+ [entityMapping setEntityIdentifier:entityIdentififer forRelationship:relationshipName];
+ }
+ }];
+}
+
+@interface RKObjectMapping (Private)
+- (NSString *)transformSourceKeyPath:(NSString *)keyPath;
+@end
+
@interface RKEntityMapping ()
@property (nonatomic, weak, readwrite) Class objectClass;
@property (nonatomic, strong) NSMutableArray *mutableConnections;
+@property (nonatomic, strong) NSMutableDictionary *relationshipNamesToEntityIdentifiers;
@end
@implementation RKEntityMapping
-
+ (id)mappingForClass:(Class)objectClass
{
@throw [NSException exceptionWithName:NSInternalInconsistencyException
@@ -60,9 +78,7 @@ - (id)initWithEntity:(NSEntityDescription *)entity
self = [self initWithClass:objectClass];
if (self) {
self.entity = entity;
-
- [self addObserver:self forKeyPath:@"entity" options:NSKeyValueObservingOptionInitial context:nil];
- [self addObserver:self forKeyPath:@"primaryKeyAttribute" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
+ RKInferIdentifiersForEntityMapping(self);
}
return self;
@@ -73,61 +89,106 @@ - (id)initWithClass:(Class)objectClass
self = [super initWithClass:objectClass];
if (self) {
self.mutableConnections = [NSMutableArray array];
+ self.relationshipNamesToEntityIdentifiers = [NSMutableDictionary dictionary];
}
return self;
}
-- (void)dealloc
+- (id)copyWithZone:(NSZone *)zone
{
- [self removeObserver:self forKeyPath:@"entity"];
- [self removeObserver:self forKeyPath:@"primaryKeyAttribute"];
+ RKEntityMapping *copy = [super copyWithZone:zone];
+ copy.entityIdentifier = [self.entityIdentifier copy];
+
+ for (RKConnectionDescription *connection in self.connections) {
+ [copy addConnection:[connection copy]];
+ }
+
+ return copy;
}
-- (RKConnectionMapping *)connectionMappingForRelationshipWithName:(NSString *)relationshipName
+- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName
{
- for (RKConnectionMapping *connection in self.connectionMappings) {
- if ([connection.relationship.name isEqualToString:relationshipName]) {
+ NSAssert([relationshipOrName isKindOfClass:[NSString class]] || [relationshipOrName isKindOfClass:[NSRelationshipDescription class]], @"Relationship specifier must be a name or a relationship description");
+ NSString *relationshipName = [relationshipOrName isKindOfClass:[NSRelationshipDescription class]] ? [(NSRelationshipDescription *)relationshipOrName name] : relationshipOrName;
+ for (RKConnectionDescription *connection in self.connections) {
+ if ([[connection.relationship name] isEqualToString:relationshipName]) {
return connection;
}
}
return nil;
}
-- (void)addConnectionMapping:(RKConnectionMapping *)mapping
+- (void)addConnection:(RKConnectionDescription *)connection
{
- NSParameterAssert(mapping);
- RKConnectionMapping *connectionMapping = [self connectionMappingForRelationshipWithName:mapping.relationship.name];
- NSAssert(connectionMapping == nil, @"Cannot add connect relationship %@ by primary key, a mapping already exists.", mapping.relationship.name);
+ NSParameterAssert(connection);
+ RKConnectionDescription *existingConnection = [self connectionForRelationship:connection.relationship];
+ NSAssert(existingConnection == nil, @"Cannot add connection: An existing connection already exists for the '%@' relationship.", connection.relationship.name);
NSAssert(self.mutableConnections, @"self.mutableConnections should not be nil");
- [self.mutableConnections addObject:mapping];
+ [self.mutableConnections addObject:connection];
+}
+
+- (void)removeConnection:(RKConnectionDescription *)connection
+{
+ [self.mutableConnections removeObject:connection];
}
-- (void)addConnectionMappingsFromArray:(NSArray *)arrayOfConnectionMappings
+- (NSArray *)connections
{
- for (RKConnectionMapping *connectionMapping in arrayOfConnectionMappings) {
- [self addConnectionMapping:connectionMapping];
+ return [NSArray arrayWithArray:self.mutableConnections];
+}
+
+- (void)addConnectionForRelationship:(id)relationshipOrName connectedBy:(id)connectionSpecifier
+{
+ NSRelationshipDescription *relationship = [relationshipOrName isKindOfClass:[NSRelationshipDescription class]] ? relationshipOrName : [self.entity relationshipsByName][relationshipOrName];
+ NSAssert(relationship, @"No relatiobship was found named '%@' in the '%@' entity", relationshipOrName, [self.entity name]);
+ RKConnectionDescription *connection = nil;
+ if ([connectionSpecifier isKindOfClass:[NSString class]]) {
+ NSString *sourceAttribute = connectionSpecifier;
+ NSString *destinationAttribute = [self transformSourceKeyPath:sourceAttribute];
+ connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:@{ sourceAttribute: destinationAttribute }];
+ } else if ([connectionSpecifier isKindOfClass:[NSArray class]]) {
+ NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:[connectionSpecifier count]];
+ for (NSString *sourceAttribute in connectionSpecifier) {
+ NSString *destinationAttribute = [self transformSourceKeyPath:sourceAttribute];
+ attributes[sourceAttribute] = destinationAttribute;
+ }
+ connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:attributes];
+ } else if ([connectionSpecifier isKindOfClass:[NSDictionary class]]) {
+ connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:connectionSpecifier];
+ } else {
+ [NSException raise:NSInvalidArgumentException format:@"Connections can only be described using `NSString`, `NSArray`, or `NSDictionary` objects. Instead, got: %@", connectionSpecifier];
}
+
+ [self.mutableConnections addObject:connection];
}
-- (RKConnectionMapping *)addConnectionMappingForRelationshipForName:(NSString *)relationshipName
- fromSourceKeyPath:(NSString *)sourceKeyPath
- toKeyPath:(NSString *)destinationKeyPath
- matcher:(RKDynamicMappingMatcher *)matcher
+- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier
{
- NSRelationshipDescription *relationship = [[self.entity propertiesByName] objectForKey:relationshipName];
- NSAssert(relationship, @"Unable to find a relationship named '%@' in the entity: %@", relationshipName, self.entity);
- RKConnectionMapping *connectionMapping = [[RKConnectionMapping alloc] initWithRelationship:relationship sourceKeyPath:sourceKeyPath destinationKeyPath:destinationKeyPath matcher:matcher];
- [self addConnectionMapping:connectionMapping];
- return connectionMapping;
+ NSAssert(entityIdentifier == nil || [entityIdentifier.entity isKindOfEntity:self.entity], @"Invalid entity identifier value: The identifier given is for the '%@' entity.", [entityIdentifier.entity name]);
+ _entityIdentifier = entityIdentifier;
}
-- (void)removeConnectionMapping:(RKConnectionMapping *)connectionMapping
+- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier forRelationship:(NSString *)relationshipName
{
- [self.mutableConnections removeObject:connectionMapping];
+ NSRelationshipDescription *relationship = [self.entity relationshipsByName][relationshipName];
+ NSAssert(relationship, @"Cannot set entity identififer for relationship '%@': no relationship found for that name.", relationshipName);
+ NSAssert([[relationship destinationEntity] isKindOfEntity:entityIdentifier.entity], @"Cannot set entity identifier for relationship '%@': the given relationship identifier is for the '%@' entity, but the '%@' entity was expected.", relationshipName, [entityIdentifier.entity name], [[relationship destinationEntity] name]);
+ self.relationshipNamesToEntityIdentifiers[relationshipName] = entityIdentifier;
}
-- (id)defaultValueForMissingAttribute:(NSString *)attributeName
+- (RKEntityIdentifier *)entityIdentifierForRelationship:(NSString *)relationshipName
+{
+ RKEntityIdentifier *entityIdentifier = self.relationshipNamesToEntityIdentifiers[relationshipName];
+ if (! entityIdentifier) {
+ RKRelationshipMapping *relationshipMapping = [self propertyMappingsByDestinationKeyPath][relationshipName];
+ entityIdentifier = [relationshipMapping.mapping isKindOfClass:[RKEntityIdentifier class]] ? [(RKEntityMapping *)relationshipMapping.mapping entityIdentifier] : nil;
+ }
+
+ return entityIdentifier;
+}
+
+- (id)defaultValueForAttribute:(NSString *)attributeName
{
NSAttributeDescription *desc = [[self.entity attributesByName] valueForKey:attributeName];
return [desc defaultValue];
@@ -143,25 +204,14 @@ - (Class)classForProperty:(NSString *)propertyName
return propertyClass;
}
-/*
- Allows the primaryKeyAttributeName property on the NSEntityDescription to configure the mapping and vice-versa
- */
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
++ (void)setEntityIdentifierInferenceEnabled:(BOOL)enabled
{
- if ([keyPath isEqualToString:@"entity"]) {
- if (! self.primaryKeyAttribute) {
- self.primaryKeyAttribute = [self.entity primaryKeyAttributeName];
- }
- } else if ([keyPath isEqualToString:@"primaryKeyAttribute"]) {
- if (! self.entity.primaryKeyAttribute) {
- self.entity.primaryKeyAttributeName = self.primaryKeyAttribute;
- }
- }
+ entityIdentifierInferenceEnabled = enabled;
}
-- (NSArray *)connectionMappings
++ (BOOL)isEntityIdentifierInferenceEnabled
{
- return [NSArray arrayWithArray:self.mutableConnections];
+ return entityIdentifierInferenceEnabled;
}
@end
View
82 Code/CoreData/RKFetchRequestManagedObjectCache.m
@@ -7,7 +7,6 @@
//
#import "RKFetchRequestManagedObjectCache.h"
-#import "NSEntityDescription+RKAdditions.h"
#import "RKLog.h"
#import "RKPropertyInspector.h"
#import "RKPropertyInspector+CoreData.h"
@@ -16,37 +15,56 @@
#undef RKLogComponent
#define RKLogComponent RKlcl_cRestKitCoreData
-@implementation RKFetchRequestManagedObjectCache
+static NSString *RKPredicateCacheKeyForAttributes(NSArray *attributeNames)
+{
+ return [[attributeNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] componentsJoinedByString:@":"];
+}
-- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
- withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
- value:(id)primaryKeyValue
- inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
+// NOTE: We build a dynamic format string here because `NSCompoundPredicate` does not support use of substiution variables
+static NSPredicate *RKPredicateWithSubsitutionVariablesForAttributes(NSArray *attributeNames)
{
- NSAssert(entity, @"Cannot find existing managed object without a target class");
- NSAssert(primaryKeyAttribute, @"Cannot find existing managed object instance without mapping that defines a primaryKeyAttribute");
- NSAssert(managedObjectContext, @"Cannot find existing managed object with a nil context");
+ NSMutableArray *formatFragments = [NSMutableArray arrayWithCapacity:[attributeNames count]];
+ for (NSString *attributeName in attributeNames) {
+ NSString *formatFragment = [NSString stringWithFormat:@"%@ = $%@", attributeName, attributeName];
+ [formatFragments addObject:formatFragment];
+ }
+
+ return [NSPredicate predicateWithFormat:[formatFragments componentsJoinedByString:@" AND "]];
+}
+
+@interface RKFetchRequestManagedObjectCache ()
+@property (nonatomic, strong) NSCache *predicateCache;
+@end
+
+@implementation RKFetchRequestManagedObjectCache
- id searchValue = primaryKeyValue;
- Class type = [[RKPropertyInspector sharedInspector] classForPropertyNamed:primaryKeyAttribute ofEntity:entity];
- if (type && ([type isSubclassOfClass:[NSString class]] && NO == [primaryKeyValue isKindOfClass:[NSString class]])) {
- searchValue = [NSString stringWithFormat:@"%@", primaryKeyValue];
- } else if (type && ([type isSubclassOfClass:[NSNumber class]] && NO == [primaryKeyValue isKindOfClass:[NSNumber class]])) {
- if ([primaryKeyValue isKindOfClass:[NSString class]]) {
- searchValue = [NSNumber numberWithDouble:[(NSString *)primaryKeyValue doubleValue]];
- }
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ self.predicateCache = [NSCache new];
}
+ return self;
+}
+
+- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
+ attributeValues:(NSDictionary *)attributeValues
+ inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
+{
- // Use cached predicate if primary key matches
- NSPredicate *predicate = nil;
- if ([entity.primaryKeyAttributeName isEqualToString:primaryKeyAttribute]) {
- predicate = [entity predicateForPrimaryKeyAttributeWithValue:searchValue];
- } else {
- // Parse a predicate
- predicate = [NSPredicate predicateWithFormat:@"%K = %@", primaryKeyAttribute, searchValue];
+ NSAssert(entity, @"Cannot find existing managed object without a target class");
+ NSAssert(attributeValues, @"Cannot retrieve cached objects without attribute values to identify them with.");
+ NSAssert(managedObjectContext, @"Cannot find existing managed object with a nil context");
+
+ NSString *predicateCacheKey = RKPredicateCacheKeyForAttributes([attributeValues allKeys]);
+ NSPredicate *substitutionPredicate = [self.predicateCache objectForKey:predicateCacheKey];
+ if (! substitutionPredicate) {
+ substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributes([attributeValues allKeys]);
+ [self.predicateCache setObject:substitutionPredicate forKey:predicateCacheKey];
}
+
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[entity name]];
- fetchRequest.predicate = predicate;
+ fetchRequest.predicate = [substitutionPredicate predicateWithSubstitutionVariables:attributeValues];
NSError *error = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (! objects) {
@@ -57,18 +75,4 @@ - (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
return objects;
}
-- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
- withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
- value:(id)primaryKeyValue
- inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
-{
- NSArray *objects = [self findInstancesOfEntity:entity withPrimaryKeyAttribute:primaryKeyAttribute value:primaryKeyValue inManagedObjectContext:managedObjectContext];
-
- NSManagedObject *object = nil;
- if ([objects count] > 0) {
- object = [objects objectAtIndex:0];
- }
- return object;
-}
-
@end
View
43 Code/CoreData/RKInMemoryManagedObjectCache.m
@@ -52,38 +52,23 @@ - (id)init
userInfo:nil];
}
-
-- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
- withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
- value:(id)primaryKeyValue
- inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
+- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
+ attributeValues:(NSDictionary *)attributeValues
+ inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
- NSAssert(self.entityCache, @"Entity cache cannot be nil.");
- if (! [self.entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
- RKLogInfo(@"Caching instances of Entity '%@' by primary key attribute '%@'", entity.name, primaryKeyAttribute);
- [self.entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
- RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
+ NSParameterAssert(entity);
+ NSParameterAssert(attributeValues);
+ NSParameterAssert(managedObjectContext);
+
+ NSArray *attributes = [attributeValues allKeys];
+ if (! [self.entityCache isEntity:entity cachedByAttributes:attributes]) {
+ RKLogInfo(@"Caching instances of Entity '%@' by attributes '%@'", entity.name, [attributes componentsJoinedByString:@", "]);
+ [self.entityCache cacheObjectsForEntity:entity byAttributes:attributes];
+ RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attributes:attributes];
RKLogTrace(@"Cached %ld objects", (long)[attributeCache count]);
}
-
- return [self.entityCache objectForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
-}
-
-- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
- withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
- value:(id)primaryKeyValue
- inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
-{
- NSAssert(self.entityCache, @"Entity cache cannot be nil.");
-
- if (! [self.entityCache isEntity:entity cachedByAttribute:primaryKeyAttribute]) {
- RKLogInfo(@"Caching instances of Entity '%@' by primary key attribute '%@'", entity.name, primaryKeyAttribute);
- [self.entityCache cacheObjectsForEntity:entity byAttribute:primaryKeyAttribute];
- RKEntityByAttributeCache *attributeCache = [self.entityCache attributeCacheForEntity:entity attribute:primaryKeyAttribute];
- RKLogTrace(@"Cached %ld objects", (long)[attributeCache count]);
- }
-
- return [self.entityCache objectsForEntity:entity withAttribute:primaryKeyAttribute value:primaryKeyValue inContext:managedObjectContext];
+
+ return [self.entityCache objectsForEntity:entity withAttributeValues:attributeValues inContext:managedObjectContext];
}
- (void)didFetchObject:(NSManagedObject *)object
View
23 Code/CoreData/RKManagedObjectCaching.h
@@ -18,6 +18,13 @@
@required
+/// @name Retrieving Managed Objects
+
+// New API
+- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity
+ attributeValues:(NSDictionary *)attributeValues
+ inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
+
///------------------------------
/// @name Finding Managed Objects
///------------------------------
@@ -33,10 +40,10 @@
@return A managed object that is an instance of the given entity with a primary key and value matching
the specified parameters, or nil if no object was found.
*/
-- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
- withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
- value:(id)primaryKeyValue
- inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
+//- (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
+// withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
+// value:(id)primaryKeyValue
+// inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
/**
Retrieves an array of model objects from the object store given a Core Data entity and
@@ -49,10 +56,10 @@
@return An array of managed objects that are instances of the given entity with a primary key and value matching
the specified parameters, or nil if no object was found.
*/
-- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
- withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
- value:(id)primaryKeyValue
- inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
+//- (NSArray *)findInstancesOfEntity:(NSEntityDescription *)entity
+// withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
+// value:(id)primaryKeyValue
+// inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext;
///---------------------------------------------------
/// @name Handling Managed Object Change Notifications
View
174 Code/CoreData/RKManagedObjectMappingOperationDataSource.m
@@ -26,8 +26,94 @@
#import "RKMappingOperation.h"
#import "RKDynamicMappingMatcher.h"
#import "RKManagedObjectCaching.h"
-#import "RKRelationshipConnectionOperation.h"
+#import "RKConnectionOperation.h"
#import "RKMappingErrors.h"
+#import "RKValueTransformers.h"
+
+extern NSString * const RKObjectMappingNestingAttributeKeyName;
+
+id RKTransformedValueWithClass(id value, Class destinationType, NSValueTransformer *dateToStringValueTransformer);
+NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id value, NSArray *propertyMappings);
+
+// Return YES if the entity is identified by an attribute that acts as the nesting key in the source representation
+static BOOL RKEntityMappingIsIdentifiedByNestingAttribute(RKEntityMapping *entityMapping)
+{
+ for (NSAttributeDescription *attribute in entityMapping.entityIdentifier.attributes) {
+ RKAttributeMapping *attributeMapping = [[entityMapping propertyMappingsByDestinationKeyPath] objectForKey:[attribute name]];
+ if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+// We always need to map the dynamic nesting attribute first so that sub-key attribute mappings apply cleanly
+static NSArray *RKEntityIdentifierAttributesInMappingOrder(RKEntityMapping *entityMapping)
+{
+ NSMutableArray *orderedAttributes = [NSMutableArray arrayWithCapacity:[entityMapping.entityIdentifier.attributes count]];
+ for (NSAttributeDescription *attribute in entityMapping.entityIdentifier.attributes) {
+ RKAttributeMapping *attributeMapping = [[entityMapping propertyMappingsByDestinationKeyPath] objectForKey:[attribute name]];
+ if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
+ // We want to map the nesting attribute first
+ [orderedAttributes insertObject:attribute atIndex:0];
+ } else {
+ [orderedAttributes addObject:attribute];
+ }
+ }
+
+ return orderedAttributes;
+}
+
+static id RKValueForAttributeMappingInRepresentation(RKAttributeMapping *attributeMapping, NSDictionary *representation)
+{
+ if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
+ return [[representation allKeys] lastObject];
+ } else {
+ return [representation valueForKeyPath:attributeMapping.sourceKeyPath];
+ }
+}
+
+static RKAttributeMapping *RKAttributeMappingForNameInMappings(NSString *name, NSArray *attributeMappings)
+{
+ for (RKAttributeMapping *attributeMapping in attributeMappings) {
+ if ([[attributeMapping destinationKeyPath] isEqualToString:name]) return attributeMapping;
+ }
+
+ return nil;
+}
+
+/**
+ This function is the workhorse for extracting entity identifier attributes from a dictionary representation. It supports type transformations, compound entity identifier attributes, and dynamic nesting keys within the representation.
+ */
+static NSDictionary *RKEntityIdentifierAttributesForEntityMappingWithRepresentation(RKEntityMapping *entityMapping, NSDictionary *representation)
+{
+ RKDateToStringValueTransformer *dateToStringTransformer = [[RKDateToStringValueTransformer alloc] initWithDateToStringFormatter:entityMapping.preferredDateFormatter
+ stringToDateFormatters:entityMapping.dateFormatters];
+ NSArray *orderedAttributes = RKEntityIdentifierAttributesInMappingOrder(entityMapping);
+ BOOL containsNestingAttribute = RKEntityMappingIsIdentifiedByNestingAttribute(entityMapping);
+ __block NSArray *attributeMappings = entityMapping.attributeMappings;
+ if (containsNestingAttribute) RKLogDebug(@"Detected use of nested dictionary key as identifying attribute");
+
+ NSMutableDictionary *entityIdentifierAttributes = [NSMutableDictionary dictionaryWithCapacity:[orderedAttributes count]];
+ [orderedAttributes enumerateObjectsUsingBlock:^(NSAttributeDescription *attribute, NSUInteger idx, BOOL *stop) {
+ RKAttributeMapping *attributeMapping = RKAttributeMappingForNameInMappings([attribute name], attributeMappings);
+ Class attributeClass = [entityMapping classForProperty:[attribute name]];
+ id attributeValue = nil;
+ if (containsNestingAttribute && idx == 0) {
+ // This is the nesting attribute
+ attributeValue = RKTransformedValueWithClass([[representation allKeys] lastObject], attributeClass, dateToStringTransformer);
+ attributeMappings = RKApplyNestingAttributeValueToMappings([attribute name], attributeValue, attributeMappings);
+ } else {
+ id sourceValue = RKValueForAttributeMappingInRepresentation(attributeMapping, representation);
+ attributeValue = RKTransformedValueWithClass(sourceValue, attributeClass, dateToStringTransformer);
+ }
+
+ [entityIdentifierAttributes setObject:attributeValue ?: [NSNull null] forKey:[attribute name]];
+ }];
+
+ return entityIdentifierAttributes;
+}
// Set Logging Component
#undef RKLogComponent
@@ -64,69 +150,41 @@ - (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRep
return [mapping.objectClass new];
}
- RKEntityMapping *entityMapping = (RKEntityMapping *)mapping;
- id object = nil;
- id primaryKeyValue = nil;
- NSString *primaryKeyAttribute;
-
- NSEntityDescription *entity = [entityMapping entity];
- RKAttributeMapping *primaryKeyAttributeMapping = nil;
-
- primaryKeyAttribute = [entityMapping primaryKeyAttribute];
- if (primaryKeyAttribute) {
- // If a primary key has been set on the object mapping, find the attribute mapping
- // so that we can extract any existing primary key from the mappable data
- for (RKAttributeMapping *attributeMapping in entityMapping.attributeMappings) {
- if ([attributeMapping.destinationKeyPath isEqualToString:primaryKeyAttribute]) {
- primaryKeyAttributeMapping = attributeMapping;
- break;
- }
- }
-
- // Get the primary key value out of the mappable data (if any)
- if ([primaryKeyAttributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
- RKLogDebug(@"Detected use of nested dictionary key as primaryKey attribute...");
- primaryKeyValue = [[representation allKeys] lastObject];
- } else {
- NSString* keyPathForPrimaryKeyElement = primaryKeyAttributeMapping.sourceKeyPath;
- if (keyPathForPrimaryKeyElement) {
- primaryKeyValue = [representation valueForKeyPath:keyPathForPrimaryKeyElement];
- } else {
- RKLogWarning(@"Unable to find source attribute for primaryKeyAttribute '%@': unable to find existing object instances by primary key.", primaryKeyAttribute);
- }
- }
- }
-
+ RKEntityMapping *entityMapping = (RKEntityMapping *)mapping;
+ NSDictionary *entityIdentifierAttributes = RKEntityIdentifierAttributesForEntityMappingWithRepresentation(entityMapping, representation);
if (! self.managedObjectCache) {
RKLogWarning(@"Performing managed object mapping with a nil managed object cache:\n"
"Unable to update existing object instances by primary key. Duplicate objects may be created.");
}
- // If we have found the primary key attribute & value, try to find an existing instance to update
- if (primaryKeyAttribute && primaryKeyValue && NO == [primaryKeyValue isEqual:[NSNull null]]) {
- object = [self.managedObjectCache findInstanceOfEntity:entity
- withPrimaryKeyAttribute:primaryKeyAttribute
- value:primaryKeyValue
- inManagedObjectContext:self.managedObjectContext];
-
- if (object && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
- [self.managedObjectCache didFetchObject:object];
+ // If we have found the entity identifier attributes, try to find an existing instance to update
+ NSEntityDescription *entity = [entityMapping entity];
+ NSManagedObject *managedObject = nil;
+ if ([entityIdentifierAttributes count]) {
+ NSArray *objects = [self.managedObjectCache managedObjectsWithEntity:entity
+ attributeValues:entityIdentifierAttributes
+ inManagedObjectContext:self.managedObjectContext];
+ if (entityMapping.entityIdentifier.predicate) objects = [objects filteredArrayUsingPredicate:entityMapping.entityIdentifier.predicate];
+ if ([objects count] > 0) {
+ managedObject = objects[0];
+ if ([objects count] > 1) RKLogWarning(@"Managed object cache returned %d objects for the identifier configured for the '%@' entity, expected 1.", [objects count], [entity name]);
+ }
+ if (managedObject && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) {
+ [self.managedObjectCache didFetchObject:managedObject];
}
}
- if (object == nil) {
- object = [[NSManagedObject alloc] initWithEntity:entity
+ if (managedObject == nil) {
+ managedObject = [[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:self.managedObjectContext];
- if (primaryKeyAttribute && primaryKeyValue && ![primaryKeyValue isEqual:[NSNull null]]) {
- [object setValue:primaryKeyValue forKey:primaryKeyAttribute];
- }
+ [managedObject setValuesForKeysWithDictionary:entityIdentifierAttributes];
if ([self.managedObjectCache respondsToSelector:@selector(didCreateObject:)]) {
- [self.managedObjectCache didCreateObject:object];
+ [self.managedObjectCache didCreateObject:managedObject];
}
}
- return object;
+ return managedObject;
}
// Mapping operations should be executed against managed object contexts with the `NSPrivateQueueConcurrencyType` concurrency type
@@ -150,27 +208,25 @@ - (BOOL)commitChangesForMappingOperation:(RKMappingOperation *)mappingOperation
if ([mappingOperation.objectMapping isKindOfClass:[RKEntityMapping class]]) {
[self emitDeadlockWarningIfNecessary];
- NSArray *connectionMappings = [(RKEntityMapping *)mappingOperation.objectMapping connectionMappings];
- if ([connectionMappings count] > 0 && self.managedObjectCache == nil) {
+ NSArray *connections = [(RKEntityMapping *)mappingOperation.objectMapping connections];
+ if ([connections count] > 0 && self.managedObjectCache == nil) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Cannot map an entity mapping that contains connection mappings with a data source whose managed object cache is nil." };
NSError *localError = [NSError errorWithDomain:RKErrorDomain code:RKMappingErrorNilManagedObjectCache userInfo:userInfo];
if (error) *error = localError;
return NO;
}
- for (RKConnectionMapping *connectionMapping in connectionMappings) {
- RKRelationshipConnectionOperation *operation = [[RKRelationshipConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject
- connectionMapping:connectionMapping
- managedObjectCache:self.managedObjectCache];
- __weak RKRelationshipConnectionOperation *weakOperation = operation;
+ for (RKConnectionDescription *connection in connections) {
+ RKConnectionOperation *operation = [[RKConnectionOperation alloc] initWithManagedObject:mappingOperation.destinationObject connection:connection managedObjectCache:self.managedObjectCache];
+ __weak RKConnectionOperation *weakOperation = operation;
[operation setCompletionBlock:^{
if (weakOperation.connectedValue) {
if ([mappingOperation.delegate respondsToSelector:@selector(mappingOperation:didConnectRelationship:withValue:usingMapping:)]) {
- [mappingOperation.delegate mappingOperation:mappingOperation didConnectRelationship:connectionMapping.relationship withValue:weakOperation.connectedValue usingMapping:connectionMapping];
+ [mappingOperation.delegate mappingOperation:mappingOperation didConnectRelationship:connection.relationship toValue:weakOperation.connectedValue usingConnection:connection];
}
} else {
if ([mappingOperation.delegate respondsToSelector:@selector(mappingOperation:didFailToConnectRelationship:usingMapping:)]) {
- [mappingOperation.delegate mappingOperation:mappingOperation didFailToConnectRelationship:connectionMapping.relationship usingMapping:connectionMapping];
+ [mappingOperation.delegate mappingOperation:mappingOperation didFailToConnectRelationship:connection.relationship usingConnection:connection];
}
}
}];
View
2  Code/Network/RKObjectParameterization.m