diff --git a/Code/CoreData/RKEntityMapping.h b/Code/CoreData/RKEntityMapping.h index f896d1a8ff..e7a2f57529 100644 --- a/Code/CoreData/RKEntityMapping.h +++ b/Code/CoreData/RKEntityMapping.h @@ -38,7 +38,7 @@ When `RKIdentificationAttributesInferredFromEntity` is invoked, the entity is first checked for a user info key specifying the identifying attributes. If the user info of the given entity contains a value for the key 'RKEntityIdentificationAttributes', then that value is used to construct an array of attributes. The user info key must contain a string or an array of strings specifying the names of attributes that exist in the given entity. - If no attributes are specified in the user info, then the entity is searched for an attribute whose name matches the llama-cased name of the entity. For example, an entity named 'Article' would have an inferred identifier attribute of 'articleID' and an entity named 'ApprovedComment' would be inferred as 'approvedCommentID'. If such an attribute is found within the entity, an array is returned containing the attribute. If none is returned, the the attributes are searched for the following names: + If no attributes are specified in the user info, then the entity is searched for an attribute whose name matches the llama-cased or snake-cased name of the entity. For example, an entity named 'Article' would have an inferred identifying attributes of 'articleID' and 'article_id', and an entity named 'ApprovedComment' would be inferred as 'approvedCommentID' and 'approved_comment_id'. If such an attribute is found within the entity, an array is returned containing the attribute. If none is returned, the the attributes are searched for the following names: 1. 'identifier' 1. 'id' diff --git a/Code/CoreData/RKEntityMapping.m b/Code/CoreData/RKEntityMapping.m index 78d5e56bfb..c88b41a762 100644 --- a/Code/CoreData/RKEntityMapping.m +++ b/Code/CoreData/RKEntityMapping.m @@ -58,12 +58,26 @@ return nil; } -// Given 'Human', returns 'humanID'; Given 'AmenityReview' returns 'amenityReviewID' -static NSString *RKEntityIdentificationAttributeNameForEntity(NSEntityDescription *entity) +static NSString *RKUnderscoredStringFromCamelCasedString(NSString *camelCasedString) +{ + NSError *error = nil; + NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"((^[a-z]+)|([A-Z]{1}[a-z]+)|([A-Z]+(?=([A-Z][a-z])|($))))" options:0 error:&error]; + if (! regularExpression) return nil; + NSMutableArray *lowercasedComponents = [NSMutableArray array]; + [regularExpression enumerateMatchesInString:camelCasedString options:0 range:NSMakeRange(0, [camelCasedString length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + [lowercasedComponents addObject:[[camelCasedString substringWithRange:[result range]] lowercaseString]]; + }]; + return [lowercasedComponents componentsJoinedByString:@"_"]; +} + +// Given 'Human', returns 'humanID' and 'human_id'; Given 'AmenityReview' returns 'amenityReviewID' and 'amenity_review_id' +static NSArray *RKEntityIdentificationAttributeNamesForEntity(NSEntityDescription *entity) { NSString *entityName = [entity name]; NSString *lowerCasedFirstCharacter = [[entityName substringToIndex:1] lowercaseString]; - return [NSString stringWithFormat:@"%@%@ID", lowerCasedFirstCharacter, [entityName substringFromIndex:1]]; + NSString *camelizedIDAttributeName = [NSString stringWithFormat:@"%@%@ID", lowerCasedFirstCharacter, [entityName substringFromIndex:1]]; + NSString *underscoredIDAttributeName = [NSString stringWithFormat:@"%@_id", RKUnderscoredStringFromCamelCasedString([entity name])]; + return @[ camelizedIDAttributeName, underscoredIDAttributeName ]; } static NSArray *RKEntityIdentificationAttributeNames() @@ -97,7 +111,7 @@ return RKArrayOfAttributesForEntityFromAttributesOrNames(entity, attributes); } - NSMutableArray *identifyingAttributes = [NSMutableArray arrayWithObject:RKEntityIdentificationAttributeNameForEntity(entity)]; + NSMutableArray *identifyingAttributes = [RKEntityIdentificationAttributeNamesForEntity(entity) mutableCopy]; [identifyingAttributes addObjectsFromArray:RKEntityIdentificationAttributeNames()]; for (NSString *attributeName in identifyingAttributes) { NSAttributeDescription *attribute = [entity attributesByName][attributeName]; diff --git a/Tests/Logic/CoreData/RKEntityMappingTest.m b/Tests/Logic/CoreData/RKEntityMappingTest.m index 2576bbd1df..3fc2b9bd0d 100644 --- a/Tests/Logic/CoreData/RKEntityMappingTest.m +++ b/Tests/Logic/CoreData/RKEntityMappingTest.m @@ -322,8 +322,6 @@ - (void)testThatInitEntityIdentifierWithEmptyAttributesRaisesException #pragma mark - Entity Identifier Inference -// TODO: The attributes to auto-detect: entityNameID, ID, identififer, url, URL - - (void)testEntityIdentifierInferenceForEntityWithLlamaCasedIDAttribute { NSEntityDescription *entity = [[NSEntityDescription alloc] init]; @@ -467,4 +465,43 @@ - (void)testEntityIdentifierInferenceFromUserInfoKeyRaisesErrorForNonexistantAtt } } +- (void)testInferenceOfSnakeCasedEntityName +{ + NSEntityDescription *entity = [[NSEntityDescription alloc] init]; + [entity setName:@"Monkey"]; + NSAttributeDescription *identifierAttribute = [NSAttributeDescription new]; + [identifierAttribute setName:@"monkey_id"]; + [entity setProperties:@[ identifierAttribute ]]; + NSArray *identificationAttributes = RKIdentificationAttributesInferredFromEntity(entity); + expect(identificationAttributes).notTo.beNil(); + NSArray *attributeNames = @[ @"monkey_id" ]; + expect([identificationAttributes valueForKey:@"name"]).to.equal(attributeNames); +} + +- (void)testInferenceOfCompoundSnakeCasedEntityName +{ + NSEntityDescription *entity = [[NSEntityDescription alloc] init]; + [entity setName:@"ArcticMonkey"]; + NSAttributeDescription *identifierAttribute = [NSAttributeDescription new]; + [identifierAttribute setName:@"arctic_monkey_id"]; + [entity setProperties:@[ identifierAttribute ]]; + NSArray *identificationAttributes = RKIdentificationAttributesInferredFromEntity(entity); + expect(identificationAttributes).notTo.beNil(); + NSArray *attributeNames = @[ @"arctic_monkey_id" ]; + expect([identificationAttributes valueForKey:@"name"]).to.equal(attributeNames); +} + +- (void)testInferenceOfSnakeCasedEntityNameWithAbbreviation +{ + NSEntityDescription *entity = [[NSEntityDescription alloc] init]; + [entity setName:@"ArcticMonkeyURL"]; + NSAttributeDescription *identifierAttribute = [NSAttributeDescription new]; + [identifierAttribute setName:@"arctic_monkey_url_id"]; + [entity setProperties:@[ identifierAttribute ]]; + NSArray *identificationAttributes = RKIdentificationAttributesInferredFromEntity(entity); + expect(identificationAttributes).notTo.beNil(); + NSArray *attributeNames = @[ @"arctic_monkey_url_id" ]; + expect([identificationAttributes valueForKey:@"name"]).to.equal(attributeNames); +} + @end