Skip to content

Commit

Permalink
Reimplement predicate caching within the RKFetchRequestManagedObjectC…
Browse files Browse the repository at this point in the history
…ache to avoid nasty crash due to malformed predicates. fixes RestKit#1141
  • Loading branch information
blakewatters committed Jan 15, 2013
1 parent 840b37a commit c98dd41
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
18 changes: 12 additions & 6 deletions Code/CoreData/RKFetchRequestManagedObjectCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
49 changes: 49 additions & 0 deletions Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ @interface RKFetchRequestMappingCacheTest : RKTestCase

@implementation RKFetchRequestMappingCacheTest

- (void)setUp
{
[RKTestFactory setUp];
}

- (void)tearDown
{
[RKTestFactory tearDown];
}

- (void)testFetchRequestMappingCacheReturnsObjectsWithNumericPrimaryKey
{
// RKCat entity. Integer prinmary key.
Expand Down Expand Up @@ -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

0 comments on commit c98dd41

Please sign in to comment.