Reimplement predicate caching within the RKFetchRequestManagedObjectCache to avoid nasty crash due to malformed predicates. fixes #1141

This commit is contained in:
Blake Watters
2013-01-14 23:01:55 -05:00
parent 840b37a4f9
commit c98dd41c2f
2 changed files with 61 additions and 6 deletions

View File

@@ -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 @@ static NSPredicate *RKPredicateWithSubsitutionVariablesForAttributeValues(NSDict
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);

View File

@@ -16,6 +16,16 @@
@implementation RKFetchRequestMappingCacheTest
- (void)setUp
{
[RKTestFactory setUp];
}
- (void)tearDown
{
[RKTestFactory tearDown];
}
- (void)testFetchRequestMappingCacheReturnsObjectsWithNumericPrimaryKey
{
// RKCat entity. Integer prinmary key.
@@ -57,4 +67,43 @@
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