mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-24 04:46:01 +08:00
Implemented support for type coercions in primaryKeyAttribute API's. closes #758
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 @@ static char primaryKeyAttributeKey, primaryKeyPredicateKey;
|
||||
|
||||
#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,16 +60,15 @@ static char primaryKeyAttributeKey, primaryKeyPredicateKey;
|
||||
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);
|
||||
@@ -62,7 +76,23 @@ static char primaryKeyAttributeKey, 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];
|
||||
}
|
||||
|
||||
@@ -138,14 +138,13 @@ RK_FIX_CATEGORY_BUG(NSManagedObject_ActiveRecord)
|
||||
}
|
||||
|
||||
+ (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 {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
// 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
|
||||
|
||||
@@ -202,17 +202,17 @@
|
||||
}
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{
|
||||
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,28 +29,28 @@
|
||||
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")));
|
||||
}
|
||||
|
||||
@@ -58,10 +58,83 @@
|
||||
{
|
||||
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
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{
|
||||
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 @@
|
||||
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 @@
|
||||
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
|
||||
|
||||
@@ -175,8 +175,9 @@
|
||||
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 @@
|
||||
[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 @@
|
||||
[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];
|
||||
|
||||
Reference in New Issue
Block a user