diff --git a/Code/CoreData/RKFetchRequestManagedObjectCache.m b/Code/CoreData/RKFetchRequestManagedObjectCache.m index aa351c0865..1effc6f778 100644 --- a/Code/CoreData/RKFetchRequestManagedObjectCache.m +++ b/Code/CoreData/RKFetchRequestManagedObjectCache.m @@ -17,13 +17,19 @@ #define RKLogComponent RKlcl_cRestKitCoreData /* - NOTE: At the moment this cache key assume that the structure of the values for each key in the `attributeValues` in constant - i.e. if you have `userID`, it will always be a single value, or `userIDs` will always be an array. - It will need to be reimplemented if changes in attribute values occur during the life of a single cache + This function computes a cache key given a dictionary of attribute values. Each attribute name is used as a fragment within the aggregate cache key. A suffix qualifier is appended that differentiates singular vs. collection attribute values so that '==' and 'IN' predicates are computed appropriately. */ -static NSString *RKPredicateCacheKeyForAttributes(NSArray *attributeNames) +static NSString *RKPredicateCacheKeyForAttributeValues(NSDictionary *attributesValues) { - return [[attributeNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] componentsJoinedByString:@":"]; + NSArray *sortedKeys = [[attributesValues allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + NSMutableArray *keyFragments = [NSMutableArray array]; + for (NSString *attributeName in sortedKeys) { + id value = [attributesValues objectForKey:attributeName]; + char suffix = ([value respondsToSelector:@selector(count)]) ? '+' : '.'; + NSString *attributeKey = [NSString stringWithFormat:@"%@%c", attributeName, suffix]; + [keyFragments addObject:attributeKey]; + } + return [keyFragments componentsJoinedByString:@":"]; } // NOTE: We build a dynamic format string here because `NSCompoundPredicate` does not support use of substiution variables @@ -65,7 +71,7 @@ - (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity 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]); + NSString *predicateCacheKey = RKPredicateCacheKeyForAttributeValues(attributeValues); NSPredicate *substitutionPredicate = [self.predicateCache objectForKey:predicateCacheKey]; if (! substitutionPredicate) { substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributeValues(attributeValues); diff --git a/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m b/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m index b9c31b265c..0297a34b60 100644 --- a/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m +++ b/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m @@ -16,6 +16,16 @@ @interface RKFetchRequestMappingCacheTest : RKTestCase @implementation RKFetchRequestMappingCacheTest +- (void)setUp +{ + [RKTestFactory setUp]; +} + +- (void)tearDown +{ + [RKTestFactory tearDown]; +} + - (void)testFetchRequestMappingCacheReturnsObjectsWithNumericPrimaryKey { // RKCat entity. Integer prinmary key. @@ -57,4 +67,43 @@ - (void)testFetchRequestMappingCacheReturnsObjectsWithStringPrimaryKey expect(managedObjects).to.equal(birthdays); } +- (void)testThatCacheCanHandleSwitchingBetweenSingularAndPluralAttributeValues +{ + // RKEvent entity. String primary key + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKFetchRequestManagedObjectCache *cache = [RKFetchRequestManagedObjectCache new]; + NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"Event" inManagedObjectStore:managedObjectStore]; + mapping.identificationAttributes = @[ @"eventID" ]; + + RKEvent *event1 = [NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + event1.eventID = @"e-1234-a8-b12"; + + RKEvent *event2 = [NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + event2.eventID = @"ff-1234-a8-b12"; + + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSSet *managedObjects = [cache managedObjectsWithEntity:entity + attributeValues:@{ @"eventID": @[ event1.eventID, event2.eventID ] } + inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + NSSet *events = [NSSet setWithObjects:event1, event2, nil]; + expect(managedObjects).to.haveCountOf(2); + expect(managedObjects).to.equal(events); + + managedObjects = [cache managedObjectsWithEntity:entity + attributeValues:@{ @"eventID": event1.eventID } + inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + events = [NSSet setWithObject:event1]; + expect(managedObjects).to.haveCountOf(1); + expect(managedObjects).to.equal(events); + + managedObjects = [cache managedObjectsWithEntity:entity + attributeValues:@{ @"eventID": @[ event1.eventID ] } + inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + events = [NSSet setWithObject:event1]; + expect(managedObjects).to.haveCountOf(1); + expect(managedObjects).to.equal(events); +} + @end