Skip to content
Browse files

Implemented support for type coercions in primaryKeyAttribute API's. …

…closes #758
  • Loading branch information...
1 parent 86ac038 commit 98c8780a3146dd98927af491e57179a4357dbc9b @blakewatters blakewatters committed May 23, 2012
View
19 Code/CoreData/NSEntityDescription+RKAdditions.h
@@ -42,7 +42,17 @@ extern NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubs
Programmatically configured values take precedence over the user info
dictionary.
*/
-@property (nonatomic, retain) NSString *primaryKeyAttribute;
+@property (nonatomic, retain) NSString *primaryKeyAttributeName;
+
+/**
+ The attribute description object for the attribute designated as the primary key for the receiver.
+ */
+@property (nonatomic, readonly) NSAttributeDescription *primaryKeyAttribute;
+
+/**
+ The class representing the value of the attribute designated as the primary key for the receiver.
+ */
+@property (nonatomic, readonly) Class primaryKeyAttributeClass;
/**
Returns a cached predicate specifying that the primary key attribute is equal to the $PRIMARY_KEY_VALUE
@@ -61,7 +71,12 @@ extern NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubs
value. This predicate is constructed by evaluating the cached predicate returned by the
predicateForPrimaryKeyAttribute with a dictionary of substitution variables specifying that
$PRIMARY_KEY_VALUE is equal to the given value.
-
+
+ **NOTE**: This method considers the type of the receiver's primary key attribute when constructing
+ the predicate. It will coerce the given value into either an NSString or an NSNumber as
+ appropriate. This behavior is a convenience to avoid annoying issues related to Core Data's
+ handling of predicates for NSString and NSNumber types that were not appropriately casted.
+
@return A predicate speciying that the value of the primary key attribute is equal to a given value.
*/
- (NSPredicate *)predicateForPrimaryKeyAttributeWithValue:(id)value;
View
50 Code/CoreData/NSEntityDescription+RKAdditions.m
@@ -12,13 +12,13 @@
NSString * const RKEntityDescriptionPrimaryKeyAttributeUserInfoKey = @"primaryKeyAttribute";
NSString * const RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable = @"PRIMARY_KEY_VALUE";
-static char primaryKeyAttributeKey, primaryKeyPredicateKey;
+static char primaryKeyAttributeNameKey, primaryKeyPredicateKey;
@implementation NSEntityDescription (RKAdditions)
- (void)setPredicateForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
{
- NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == $PRIMARY_KEY_VALUE", primaryKeyAttribute];
+ NSPredicate *predicate = (primaryKeyAttribute) ? [NSPredicate predicateWithFormat:@"%K == $PRIMARY_KEY_VALUE", primaryKeyAttribute] : nil;
objc_setAssociatedObject(self,
&primaryKeyPredicateKey,
predicate,
@@ -27,10 +27,25 @@ - (void)setPredicateForPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
#pragma mark - Public
-- (NSString *)primaryKeyAttribute
+- (NSAttributeDescription *)primaryKeyAttribute
+{
+ return [[self attributesByName] valueForKey:[self primaryKeyAttributeName]];
+}
+
+- (Class)primaryKeyAttributeClass
+{
+ NSAttributeDescription *attributeDescription = [self primaryKeyAttribute];
+ if (attributeDescription) {
+ return NSClassFromString(attributeDescription.attributeValueClassName);
+ }
+
+ return nil;
+}
+
+- (NSString *)primaryKeyAttributeName
{
// Check for an associative object reference
- NSString *primaryKeyAttribute = (NSString *) objc_getAssociatedObject(self, &primaryKeyAttributeKey);
+ NSString *primaryKeyAttribute = (NSString *) objc_getAssociatedObject(self, &primaryKeyAttributeNameKey);
// Fall back to the userInfo dictionary
if (! primaryKeyAttribute) {
@@ -45,24 +60,39 @@ - (NSString *)primaryKeyAttribute
return primaryKeyAttribute;
}
-- (void)setPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
+- (void)setPrimaryKeyAttributeName:(NSString *)primaryKeyAttributeName
{
objc_setAssociatedObject(self,
- &primaryKeyAttributeKey,
- primaryKeyAttribute,
+ &primaryKeyAttributeNameKey,
+ primaryKeyAttributeName,
OBJC_ASSOCIATION_RETAIN);
- [self setPredicateForPrimaryKeyAttribute:primaryKeyAttribute];
+ [self setPredicateForPrimaryKeyAttribute:primaryKeyAttributeName];
}
-
- (NSPredicate *)predicateForPrimaryKeyAttribute
{
return (NSPredicate *) objc_getAssociatedObject(self, &primaryKeyPredicateKey);
}
- (NSPredicate *)predicateForPrimaryKeyAttributeWithValue:(id)value
{
- NSDictionary *variables = [NSDictionary dictionaryWithObject:value
+ id searchValue = value;
+ Class theClass = [self primaryKeyAttributeClass];
+ if (theClass) {
+ // TODO: This coercsion behave should be pluggable and reused from the mapper
+ if ([theClass isSubclassOfClass:[NSNumber class]] && ![searchValue isKindOfClass:[NSNumber class]]) {
+ // Handle NSString -> NSNumber
+ if ([searchValue isKindOfClass:[NSString class]]) {
+ searchValue = [NSNumber numberWithDouble:[searchValue doubleValue]];
+ }
+ } else if ([theClass isSubclassOfClass:[NSString class]] && ![searchValue isKindOfClass:[NSString class]]) {
+ // Coerce to string
+ if ([searchValue respondsToSelector:@selector(stringValue)]) {
+ searchValue = [searchValue stringValue];
+ }
+ }
+ }
+ NSDictionary *variables = [NSDictionary dictionaryWithObject:searchValue
forKey:RKEntityDescriptionPrimaryKeyAttributeValuePredicateSubstitutionVariable];
return [[self predicateForPrimaryKeyAttribute] predicateWithSubstitutionVariables:variables];
}
View
11 Code/CoreData/NSManagedObject+ActiveRecord.m
@@ -138,14 +138,13 @@ - (BOOL)isNew {
}
+ (id)findByPrimaryKey:(id)primaryKeyValue inContext:(NSManagedObjectContext *)context {
- NSEntityDescription *entity = [self entityDescriptionInContext:context];
- NSString *primaryKeyAttribute = entity.primaryKeyAttribute;
- if (! primaryKeyAttribute) {
- RKLogWarning(@"Attempt to findByPrimaryKey for entity with nil primaryKeyAttribute. Set the primaryKeyAttribute and try again! %@", entity);
+ NSPredicate *predicate = [[self entityDescriptionInContext:context] predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
+ if (! predicate) {
+ RKLogWarning(@"Attempt to findByPrimaryKey for entity with nil primaryKeyAttribute. Set the primaryKeyAttributeName and try again! %@", self);
return nil;
}
-
- return [self findFirstByAttribute:primaryKeyAttribute withValue:primaryKeyValue inContext:context];
+
+ return [self findFirstWithPredicate:predicate inContext:context];
}
+ (id)findByPrimaryKey:(id)primaryKeyValue {
View
2 Code/CoreData/RKFetchRequestManagedObjectCache.m
@@ -41,7 +41,7 @@ - (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
// Use cached predicate if primary key matches
NSPredicate *predicate = nil;
- if ([entity.primaryKeyAttribute isEqualToString:primaryKeyAttribute]) {
+ if ([entity.primaryKeyAttributeName isEqualToString:primaryKeyAttribute]) {
predicate = [entity predicateForPrimaryKeyAttributeWithValue:searchValue];
} else {
// Parse a predicate
View
6 Code/CoreData/RKManagedObjectMapping.m
@@ -202,17 +202,17 @@ - (Class)classForProperty:(NSString*)propertyName {
}
/*
- Allows the primaryKeyAttribute property on the NSEntityDescription to configure the mapping and vice-versa
+ 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
{
if ([keyPath isEqualToString:@"entity"]) {
if (! self.primaryKeyAttribute) {
- self.primaryKeyAttribute = [self.entity primaryKeyAttribute];
+ self.primaryKeyAttribute = [self.entity primaryKeyAttributeName];
}
} else if ([keyPath isEqualToString:@"primaryKeyAttribute"]) {
if (! self.entity.primaryKeyAttribute) {
- self.entity.primaryKeyAttribute = self.primaryKeyAttribute;
+ self.entity.primaryKeyAttributeName = self.primaryKeyAttribute;
}
}
}
View
93 Tests/Logic/CoreData/NSEntityDescription+RKAdditionsTest.m
@@ -19,7 +19,7 @@ - (void)testRetrievalOfPrimaryKeyFromXcdatamodel
{
RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCat" inManagedObjectContext:objectStore.primaryManagedObjectContext];
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"railsID")));
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"railsID")));
}
- (void)testRetrievalOfUnconfiguredPrimaryKeyAttributeReturnsNil
@@ -29,39 +29,112 @@ - (void)testRetrievalOfUnconfiguredPrimaryKeyAttributeReturnsNil
assertThat(entity.primaryKeyAttribute, is(nilValue()));
}
-- (void)testSettingPrimaryKeyAttributeProgramatically
+- (void)testSettingPrimaryKeyAttributeNameProgramatically
{
RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKHouse" inManagedObjectContext:objectStore.primaryManagedObjectContext];
- entity.primaryKeyAttribute = @"houseID";
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"houseID")));
+ entity.primaryKeyAttributeName = @"houseID";
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"houseID")));
}
-- (void)testSettingExistingPrimaryKeyAttributeProgramatically
+- (void)testSettingExistingPrimaryKeyAttributeNameProgramatically
{
RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCat" inManagedObjectContext:objectStore.primaryManagedObjectContext];
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"railsID")));
- entity.primaryKeyAttribute = @"catID";
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"catID")));
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"railsID")));
+ entity.primaryKeyAttributeName = @"catID";
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"catID")));
}
- (void)testSettingPrimaryKeyAttributeCreatesCachedPredicate
{
RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCat" inManagedObjectContext:objectStore.primaryManagedObjectContext];
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"railsID")));
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"railsID")));
assertThat([entity.predicateForPrimaryKeyAttribute predicateFormat], is(equalTo(@"railsID == $PRIMARY_KEY_VALUE")));
}
- (void)testThatPredicateForPrimaryKeyAttributeWithValueReturnsUsablePredicate
{
RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCat" inManagedObjectContext:objectStore.primaryManagedObjectContext];
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"railsID")));
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"railsID")));
NSNumber *primaryKeyValue = [NSNumber numberWithInt:12345];
NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:primaryKeyValue];
assertThat([predicate predicateFormat], is(equalTo(@"railsID == 12345")));
}
+- (void)testThatPredicateForPrimaryKeyAttributeCastsStringValueToNumber
+{
+ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCat" inManagedObjectContext:objectStore.primaryManagedObjectContext];
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"railsID")));
+ NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:@"12345"];
+ assertThat([predicate predicateFormat], is(equalTo(@"railsID == 12345")));
+}
+
+- (void)testThatPredicateForPrimaryKeyAttributeCastsNumberToString
+{
+ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKHouse" inManagedObjectContext:objectStore.primaryManagedObjectContext];
+ entity.primaryKeyAttributeName = @"city";
+ NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:[NSNumber numberWithInteger:12345]];
+ assertThat([predicate predicateFormat], is(equalTo(@"city == \"12345\"")));
+}
+
+- (void)testThatPredicateForPrimaryKeyAttributeReturnsNilForEntityWithoutPrimaryKey
+{
+ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKHouse" inManagedObjectContext:objectStore.primaryManagedObjectContext];
+ entity.primaryKeyAttributeName = nil;
+ NSPredicate *predicate = [entity predicateForPrimaryKeyAttributeWithValue:@"12345"];
+ assertThat([predicate predicateFormat], is(nilValue()));
+}
+
+- (void)testRetrievalOfPrimaryKeyAttributeReturnsNilIfNotSet
+{
+ NSEntityDescription *entity = [NSEntityDescription new];
+ assertThat(entity.primaryKeyAttribute, is(nilValue()));
+}
+
+- (void)testRetrievalOfPrimaryKeyAttributeReturnsNilWhenSetToInvalidAttributeName
+{
+ NSEntityDescription *entity = [NSEntityDescription new];
+ entity.primaryKeyAttributeName = @"invalidName!";
+ assertThat(entity.primaryKeyAttribute, is(nilValue()));
+}
+
+- (void)testRetrievalOfPrimaryKeyAttributeForValidAttributeName
+{
+ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCat" inManagedObjectContext:objectStore.primaryManagedObjectContext];
+ entity.primaryKeyAttributeName = @"railsID";
+ NSAttributeDescription *attribute = entity.primaryKeyAttribute;
+ assertThat(attribute, is(notNilValue()));
+ assertThat(attribute.name, is(equalTo(@"railsID")));
+ assertThat(attribute.attributeValueClassName, is(equalTo(@"NSNumber")));
+}
+
+- (void)testRetrievalOfPrimaryKeyAttributeClassReturnsNilIfNotSet
+{
+ NSEntityDescription *entity = [NSEntityDescription new];
+ assertThat([entity primaryKeyAttributeClass], is(nilValue()));
+}
+
+- (void)testRetrievalOfPrimaryKeyAttributeClassReturnsNilWhenSetToInvalidAttributeName
+{
+ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKHouse" inManagedObjectContext:objectStore.primaryManagedObjectContext];
+ entity.primaryKeyAttributeName = @"invalid";
+ assertThat([entity primaryKeyAttributeClass], is(nilValue()));
+}
+
+- (void)testRetrievalOfPrimaryKeyAttributeClassForValidAttributeName
+{
+ RKManagedObjectStore *objectStore = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKHouse" inManagedObjectContext:objectStore.primaryManagedObjectContext];
+ entity.primaryKeyAttributeName = @"railsID";
+ assertThat([entity primaryKeyAttributeClass], is(equalTo([NSNumber class])));
+}
+
@end
View
18 Tests/Logic/CoreData/NSManagedObject+ActiveRecordTest.m
@@ -20,7 +20,7 @@ - (void)testFindByPrimaryKey
{
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
NSEntityDescription *entity = [RKHuman entityDescription];
- entity.primaryKeyAttribute = @"railsID";
+ entity.primaryKeyAttributeName = @"railsID";
RKHuman *human = [RKHuman createEntity];
human.railsID = [NSNumber numberWithInt:12345];
@@ -35,7 +35,7 @@ - (void)testFindByPrimaryKeyInContext
RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
NSManagedObjectContext *context = [[RKTestFactory managedObjectStore] newManagedObjectContext];
NSEntityDescription *entity = [RKHuman entityDescription];
- entity.primaryKeyAttribute = @"railsID";
+ entity.primaryKeyAttributeName = @"railsID";
RKHuman *human = [RKHuman createInContext:context];
human.railsID = [NSNumber numberWithInt:12345];
@@ -48,4 +48,18 @@ - (void)testFindByPrimaryKeyInContext
assertThat(foundHuman, is(equalTo(human)));
}
+- (void)testFindByPrimaryKeyWithStringValueForNumericProperty
+{
+ RKManagedObjectStore *store = [RKTestFactory managedObjectStore];
+ NSEntityDescription *entity = [RKHuman entityDescription];
+ entity.primaryKeyAttributeName = @"railsID";
+
+ RKHuman *human = [RKHuman createEntity];
+ human.railsID = [NSNumber numberWithInt:12345];
+ [store save:nil];
+
+ RKHuman *foundHuman = [RKHuman findByPrimaryKey:@"12345" inContext:store.primaryManagedObjectContext];
+ assertThat(foundHuman, is(equalTo(human)));
+}
+
@end
View
9 Tests/Logic/CoreData/RKManagedObjectMappingTest.m
@@ -175,8 +175,9 @@ - (void)testThatAssigningAPrimaryKeyAttributeToAMappingWhoseEntityHasANilPrimary
NSEntityDescription *entity = [NSEntityDescription entityForName:@"RKCloud" inManagedObjectContext:store.primaryManagedObjectContext];
RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForEntity:entity inManagedObjectStore:store];
assertThat(mapping.primaryKeyAttribute, is(nilValue()));
- mapping.primaryKeyAttribute = @"cloudID";
- assertThat(entity.primaryKeyAttribute, is(equalTo(@"cloudID")));
+ mapping.primaryKeyAttribute = @"name";
+ assertThat(entity.primaryKeyAttributeName, is(equalTo(@"name")));
+ assertThat(entity.primaryKeyAttribute, is(notNilValue()));
}
#pragma mark - Fetched Results Cache
@@ -271,7 +272,7 @@ - (void)testMappingWithFetchRequestCacheWherePrimaryKeyAttributeOfMappingDisagre
[RKHuman truncateAll];
RKManagedObjectMapping* mapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:store];
mapping.primaryKeyAttribute = @"name";
- [RKHuman entity].primaryKeyAttribute = @"railsID";
+ [RKHuman entity].primaryKeyAttributeName = @"railsID";
[mapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"monkey.name" toKeyPath:@"name"]];
[RKHuman truncateAll];
@@ -297,7 +298,7 @@ - (void)testMappingWithInMemoryCacheWherePrimaryKeyAttributeOfMappingDisagreesWi
[RKHuman truncateAll];
RKManagedObjectMapping* mapping = [RKManagedObjectMapping mappingForClass:[RKHuman class] inManagedObjectStore:store];
mapping.primaryKeyAttribute = @"name";
- [RKHuman entity].primaryKeyAttribute = @"railsID";
+ [RKHuman entity].primaryKeyAttributeName = @"railsID";
[mapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"monkey.name" toKeyPath:@"name"]];
[RKHuman truncateAll];

0 comments on commit 98c8780

Please sign in to comment.
Something went wrong with that request. Please try again.