So I was writing some tests for my KidChart app, which uses core data, and I wasted a ton of time, so I thought I'd post to warn people. I wanted to avoid having to reset the state for each test, and I wanted the tests to run quickly, so I used NSInMemoryStoreType for my persistent store in my unit tests. This is a technique I've used before in other programming languages, and I was new to Core Data, so I was applying what I had done before to something I had insufficiently researched.
The app has a pretty simple data model. There are Events, and each Event has a Child and a Behavior, and each Behavior has a good/bad score attached to it. On my home screen, there's a summary view, where I show the number of events for each child for each (good/bad) score for that day. I was thinking I could use an NSPredicate to grab just what I wanted. Basically, what I wanted was:
1 SELECT Child.Name, Behavior.Score,count(Event.*)
2 FROM Child, Behavior, Event
3 WHERE Child.event.id = Event.id AND
4 Event.Behavior_id = Behavior.id AND
5 Event.date > 'Today at Midnight'
6 GROUP BY Child.Name, Behavior.Score
Except you can't have direct SQL access. So what I ended up with (after much iteration) was:
1 NSFetchRequest *request = [[NSFetchRequest alloc] init];
2 NSEntityDescription *entity = [NSEntityDescription entityForName:@"Child"
4 [request setEntity:entity];
6 // Specify that the request should return dictionaries.
7 [request setResultType:NSDictionaryResultType];
9 // Create an expression for the key path.
10 NSExpression *childEventKeyPathExpression = [NSExpression expressionForKeyPath:
12 NSExpression *onlyHappyEvents = [NSExpression expressionForSubquery:
15 predicate:[NSPredicate predicateWithFormat:@"$e.
16 behavior.score == 1 and $e.timeStamp > %@"
17 argumentArray:[NSArray arrayWithObject: currentDate]]];
19 // Create an expression to represent the count value at the key path 'events'
20 NSExpression *happyCountExpression = [NSExpression expressionForFunction:@"count:"
21 arguments:[NSArray arrayWithObject:
25 // Create an expression description using the minExpression and returning a date.
26 NSExpressionDescription *happyCountExpressionDescription = [[NSExpressionDescription
27 alloc] init];
29 // The name is the key that will be used in the dictionary for the return value.
30 [happyCountExpressionDescription setName:@"happyEventCount"];
31 [happyCountExpressionDescription setExpression:happyCountExpression];
32 [happyCountExpressionDescription setExpressionResultType:NSInteger16AttributeType];
34 NSExpression *onlySadEvents = [NSExpression expressionForSubquery:
37 predicate:[NSPredicate predicateWithFormat:@"$e.
38 behavior.score == -1 and $e.timeStamp > %@"
39 argumentArray:[NSArray arrayWithObject: currentDate]]];
40 // Create an expression to represent the count value at the key path 'events'
41 NSExpression *sadCountExpression = [NSExpression expressionForFunction:@"count:"
42 arguments:[NSArray arrayWithObject:onlySadEvents]]
45 // Create an expression description using the minExpression and returning a date.
46 NSExpressionDescription *sadCountExpressionDescription = [[NSExpressionDescription
47 alloc] init];
49 // The name is the key that will be used in the dictionary for the return value.
50 [sadCountExpressionDescription setName:@"sadEventCount"];
51 [sadCountExpressionDescription setExpression:sadCountExpression];
52 [sadCountExpressionDescription setExpressionResultType:NSInteger16AttributeType];
54 NSExpression *childNameKeyPathExpression = [NSExpression expressionForKeyPath:
56 NSExpressionDescription *nameExpressionDescription = [[NSExpressionDescription alloc]
58 [nameExpressionDescription setName:@"childName"];
59 [nameExpressionDescription setExpression:childNameKeyPathExpression];
60 [nameExpressionDescription setExpressionResultType:NSStringAttributeType];
62 // Set the request's properties to fetch just the property represented by the
64 [request setPropertiesToFetch:[NSArray arrayWithObjects:nameExpressionDescription,
65 happyCountExpressionDescription, sadCountExpressionDescription, nil]];
67 // Execute the fetch.
68 NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
69 STAssertNil(error, @"error not nil:%@",error);
Which is way more verbose than the corresponding SQL, but looks scarier than it actually is. And it worked great. And then, I changed my persistent store type to NSSQLiteStoreType. And it started throwing "NSInvalidArgumentException: -predicate only defined for abstract class" errors. And I got to start all over.