mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-23 20:31:13 +08:00
Eliminate the RKEntityIdentifier class and migrate the functionality into properties on RKEntityMapping
This commit is contained in:
@@ -1,104 +0,0 @@
|
||||
//
|
||||
// RKEntityIdentifier.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 11/20/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
/**
|
||||
The name of a key in the user info dictionary of a `NSEntityDescription` specifying the name or one or more attributes to be used to infer an entity identifier. The value of this string is 'RKEntityIdentifierAttributes'.
|
||||
*/
|
||||
extern NSString * const RKEntityIdentifierUserInfoKey;
|
||||
|
||||
@class RKManagedObjectStore;
|
||||
|
||||
/**
|
||||
The `RKEntityIdentifier` object describes a means for uniquely identifying one or more `NSManagedObject` objects within a Core Data managed object model. Entity identifiers are used by RestKit to identify existing managed objects that are to be updated while performing object mapping with an `RKEntityMapping` object. Entity identifiers identify objects by specifying the attributes that can be used to uniquely differentiate managed objects within a context.
|
||||
*/
|
||||
@interface RKEntityIdentifier : NSObject
|
||||
|
||||
///----------------------------------------
|
||||
/// @name Initializing an Entity Identifier
|
||||
///----------------------------------------
|
||||
|
||||
/**
|
||||
Creates and returns a new entity identifier with the entity for the given name and attributes in the specified managed object store.
|
||||
|
||||
@param entityName The name of the entity being identified.
|
||||
@param attributes An array of `NSString` objects containing the names of attributes or `NSAttributeDescription` objects specifying the identifying attributes for the entity.
|
||||
@param managedObjectStore The managed object store containing the managed object model within which the entity exists.
|
||||
@return A new entity identifier, configured with the given entity for the given name and the array of attributes.
|
||||
*/
|
||||
+ (id)identifierWithEntityName:(NSString *)entityName attributes:(NSArray *)attributes inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore;
|
||||
|
||||
/**
|
||||
Initializes the receiver with a given entity and array of attributes.
|
||||
|
||||
@param entity The entity being identified.
|
||||
@param attributes An array of `NSString` objects containing the names of attributes or `NSAttributeDescription` objects specifying the identifying attributes for the entity.
|
||||
@return The receiver, initialized with the given entity and attributes.
|
||||
*/
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributes;
|
||||
|
||||
///--------------------------------
|
||||
/// @name Accessing Entity Identity
|
||||
///--------------------------------
|
||||
|
||||
/**
|
||||
The entity that the receiver identifies.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSEntityDescription *entity;
|
||||
|
||||
/**
|
||||
An array of `NSAttributeDescription` objects specifying the attributes used for identification.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSArray *attributes;
|
||||
|
||||
///---------------------------------------
|
||||
/// @name Filtering the Identified Objects
|
||||
///---------------------------------------
|
||||
|
||||
/**
|
||||
An optional predicate for filtering identified objects.
|
||||
*/
|
||||
@property (nonatomic, copy) NSPredicate *predicate;
|
||||
|
||||
///-------------------------------------------
|
||||
/// @name Inferring Identifiers from the Model
|
||||
///-------------------------------------------
|
||||
|
||||
/**
|
||||
Creates and returns an entity identifier for the given entity inferred from the managed object model.
|
||||
|
||||
When inferring an entity identifier, 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 'RKEntityIdentifierAttributes', then that value is used to construct an entity identifier. 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 identifier is returned specifying that attribute. If none is returned, the the attributes are search for the following names:
|
||||
|
||||
1. 'identifier'
|
||||
1. 'ID'
|
||||
1. 'URL'
|
||||
1. 'url'
|
||||
|
||||
If any of these attributes are found, then an entity identifier is created for that attribute. If all possible inferred attributes are exhausted, then `nil` is returned.
|
||||
|
||||
@param entity The entity to infer an identifier for.
|
||||
@return An entity identifier inferred from the model for the given entity, or `nil` if none could be inferred.
|
||||
*/
|
||||
+ (RKEntityIdentifier *)inferredIdentifierForEntity:(NSEntityDescription *)entity;
|
||||
|
||||
@end
|
||||
@@ -1,129 +0,0 @@
|
||||
//
|
||||
// RKEntityIdentifier.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 11/20/12.
|
||||
// Copyright (c) 2012 RestKit. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import "RKEntityIdentifier.h"
|
||||
#import "RKManagedObjectStore.h"
|
||||
|
||||
NSString * const RKEntityIdentifierUserInfoKey = @"RKEntityIdentifierAttributes";
|
||||
|
||||
static NSArray *RKEntityIdentifierAttributesFromUserInfoOfEntity(NSEntityDescription *entity)
|
||||
{
|
||||
id userInfoValue = [entity userInfo][RKEntityIdentifierUserInfoKey];
|
||||
if (userInfoValue) {
|
||||
NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
|
||||
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
|
||||
[attributeNames enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
|
||||
if (! [attributeName isKindOfClass:[NSString class]]) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid value given in user info key '%@' of entity '%@': expected an `NSString` or `NSArray` of strings, instead got '%@' (%@)", RKEntityIdentifierUserInfoKey, [entity name], attributeName, [attributeName class]];
|
||||
}
|
||||
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeName];
|
||||
if (! attribute) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid identifier attribute specified in user info key '%@' of entity '%@': no attribue was found with the name '%@'", RKEntityIdentifierUserInfoKey, [entity name], attributeName];
|
||||
}
|
||||
|
||||
[attributes addObject:attribute];
|
||||
}];
|
||||
return attributes;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Given 'Human', returns 'humanID'; Given 'AmenityReview' returns 'amenityReviewID'
|
||||
static NSString *RKEntityIdentifierAttributeNameForEntity(NSEntityDescription *entity)
|
||||
{
|
||||
NSString *entityName = [entity name];
|
||||
NSString *lowerCasedFirstCharacter = [[entityName substringToIndex:1] lowercaseString];
|
||||
return [NSString stringWithFormat:@"%@%@ID", lowerCasedFirstCharacter, [entityName substringFromIndex:1]];
|
||||
}
|
||||
|
||||
static NSArray *RKEntityIdentifierAttributeNames()
|
||||
{
|
||||
return [NSArray arrayWithObjects:@"identifier", @"ID", @"URL", @"url", nil];
|
||||
}
|
||||
|
||||
static NSArray *RKArrayOfAttributesForEntityFromAttributesOrNames(NSEntityDescription *entity, NSArray *attributesOrNames)
|
||||
{
|
||||
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributesOrNames count]];
|
||||
for (id attributeOrName in attributesOrNames) {
|
||||
if ([attributeOrName isKindOfClass:[NSAttributeDescription class]]) {
|
||||
if (! [[entity properties] containsObject:attributeOrName]) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute value '%@' given for entity identifer: not found in the '%@' entity", attributeOrName, [entity name]];
|
||||
[attributes addObject:attributeOrName];
|
||||
} else if ([attributeOrName isKindOfClass:[NSString class]]) {
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeOrName];
|
||||
if (!attribute) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute '%@': no attribute was found for the given name in the '%@' entity.", attributeOrName, [entity name]];
|
||||
[attributes addObject:attribute];
|
||||
} else {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid value provided for entity identifier attribute: Acceptable values are either `NSAttributeDescription` or `NSString` objects."];
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@interface RKEntityIdentifier ()
|
||||
@property (nonatomic, strong, readwrite) NSEntityDescription *entity;
|
||||
@property (nonatomic, copy, readwrite) NSArray *attributes;
|
||||
@end
|
||||
|
||||
@implementation RKEntityIdentifier
|
||||
|
||||
+ (id)identifierWithEntityName:(NSString *)entityName attributes:(NSArray *)attributes inManagedObjectStore:(RKManagedObjectStore *)managedObjectStore
|
||||
{
|
||||
NSEntityDescription *entity = [managedObjectStore.managedObjectModel entitiesByName][entityName];
|
||||
return [[self alloc] initWithEntity:entity attributes:attributes];
|
||||
}
|
||||
|
||||
- (id)initWithEntity:(NSEntityDescription *)entity attributes:(NSArray *)attributes
|
||||
{
|
||||
NSParameterAssert(entity);
|
||||
NSParameterAssert(attributes);
|
||||
NSAssert([attributes count], @"At least one attribute must be provided to identify managed objects");
|
||||
self = [self init];
|
||||
if (self) {
|
||||
self.entity = entity;
|
||||
self.attributes = RKArrayOfAttributesForEntityFromAttributesOrNames(entity, attributes);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Idetifier Inference
|
||||
|
||||
+ (RKEntityIdentifier *)inferredIdentifierForEntity:(NSEntityDescription *)entity
|
||||
{
|
||||
NSArray *attributes = RKEntityIdentifierAttributesFromUserInfoOfEntity(entity);
|
||||
if (attributes) {
|
||||
return [[RKEntityIdentifier alloc] initWithEntity:entity attributes:attributes];
|
||||
}
|
||||
|
||||
NSMutableArray *identifyingAttributes = [NSMutableArray arrayWithObject:RKEntityIdentifierAttributeNameForEntity(entity)];
|
||||
[identifyingAttributes addObjectsFromArray:RKEntityIdentifierAttributeNames()];
|
||||
for (NSString *attributeName in identifyingAttributes) {
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeName];
|
||||
if (attribute) {
|
||||
return [[RKEntityIdentifier alloc] initWithEntity:entity attributes:@[ attribute ]];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -22,7 +22,6 @@
|
||||
#import "RKObjectMapping.h"
|
||||
#import "RKConnectionDescription.h"
|
||||
#import "RKMacros.h"
|
||||
#import "RKEntityIdentifier.h"
|
||||
|
||||
@class RKManagedObjectStore;
|
||||
|
||||
@@ -31,17 +30,30 @@
|
||||
|
||||
## Entity Identification
|
||||
|
||||
One of the fundamental problems when object mapping representations into Core Data entities is determining if a new object should be created or an existing object should be updated. The `RKEntityIdentifier` class describes how existing objects should be identified when an entity mapping is being applied. Entity identifiers specify one or more attributes of the entity that are to be used to identify existing objects. Typically the values of these attributes are populated by attribute mappings. It is common practice to use a single attribute corresponding to the primary key of the remote resource being mapped, but an arbitrary number of attributes may be specified for identification. Identifying attributes have all type transformations support by the mapper applied before the managed object context is search, supporting such use-cases as using an `NSDate` as an identifying attribute whose value is mapped from an `NSString`.
|
||||
One of the fundamental problems when object mapping representations into Core Data entities is determining if a new object should be created or an existing object should be updated. In an entity mapping, one or more attributes can be designated as being used for identification purposes via the `identificationAttributes` property. Typically the values of these attributes are populated by attribute mappings. It is common practice to use a single attribute corresponding to the primary key of the remote resource being mapped, but an arbitrary number of attributes may be specified for identification. Identifying attributes have all type transformations support by the mapper applied before the managed object context is searched, supporting such use-cases as using an `NSDate` as an identifying attribute whose value is mapped from an `NSString`. Identified objects can be further constrained by configuring an identification predicate via the `identificationPredicate` property. The predicate is applied after the managed object has been searched.
|
||||
|
||||
### Inferring Entity Identifiers
|
||||
### Identification Inference
|
||||
|
||||
The `RKEntityMapping` class provides support for inferring an entity identifier from the managed object model. When inference is enabled (the default state), the entity is searched for several commonly used identifying attributes and if any is found, an `entityIdentifier` is automatically configured. Inference is performed by the `[RKEntityIdentifier inferredIdentifierForEntity:` method and the inference rules are detailed in the accompanying documentation.
|
||||
The `RKEntityMapping` class provides support for inferring identification attributes from the managed object model. When inference is enabled (the default state), the entity is searched for several commonly used identifying attributes and if any is found, the value of the `identificationAttributes` property is automatically configured. Inference is performed by the `RKIdentificationAttributesInferredFromEntity` function.
|
||||
|
||||
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:
|
||||
|
||||
1. 'identifier'
|
||||
1. 'id'
|
||||
1. 'ID'
|
||||
1. 'URL'
|
||||
1. 'url'
|
||||
|
||||
If any of these attributes are found, then an array is returned containing the attribute. If all possible inferred attributes are exhausted, then `nil` is returned.
|
||||
|
||||
Note that inference will only return a single attribute. Compound attributes must be configured manually via the `identificationAttributes` property.
|
||||
|
||||
## Connecting Relationships
|
||||
|
||||
When modeling an API into Core Data representation, a common problem is that managed objects that are semantically related are loaded across discrete requests, leaving the Core Data relationships empty. The `RKConnectionDescription` class provides a means for expressing a connection between entities using corresponding attribute values or by key path. Please refer to the documentation accompanying the `RKConnectionDescription` class and the `addConnectionForRelationship:connectedBy:` method of this class.
|
||||
|
||||
@see `RKEntityIdentifier`
|
||||
@see `RKConnectionDescription`
|
||||
*/
|
||||
@interface RKEntityMapping : RKObjectMapping
|
||||
@@ -87,31 +99,21 @@
|
||||
///----------------------------------
|
||||
|
||||
/**
|
||||
The entity identifier used to identify `NSManagedObject` instances for the receiver's entity by one or more attributes.
|
||||
The array of `NSAttributeDescription` objects specifying the attributes of the receiver's entity that are used during mapping to determine whether an existing object should be updated or a new managed object should be inserted. Please see the "Entity Identification" section of this document for more information.
|
||||
|
||||
The entity identifier is used during mapping to determine whether an existing object should be updated or a new managed object should be inserted. Please see the "Entity Identification" section of this document for more information.
|
||||
@return An array of identifying attributes or `nil` if none have been configured.
|
||||
@raises NSInvalidArgumentException Raised if the setter is invoked with the name of an attribute or an `NSAttributeDescription` that does not exist in the receiver's entity. Also raised if the setter is invoked with an empty array.
|
||||
@warning Note that for convenience, this property may be set with an array containing `NSAttributeDescription` objects or `NSString` objects specifying the names of attributes that exist within the receiver's entity. The getter will always return an array of `NSAttributeDescription` objects.
|
||||
*/
|
||||
@property (nonatomic, copy) RKEntityIdentifier *entityIdentifier;
|
||||
@property (nonatomic, copy) NSArray *identificationAttributes;
|
||||
|
||||
/**
|
||||
Sets an entity identifier for the relationship with the given name.
|
||||
An optional predicate used to filter identified objects during mapping.
|
||||
|
||||
When mapping the specified relationship, the given entity identifier will be used to find existing managed object instances. If no identifier is specified, then the entity identifier of the entity mapping is used by default. This method need only be invoked if the relationship has specific identification needs that diverge from the entity.
|
||||
|
||||
@param entityIdentifier The entity identifier to be used when mapping the specified relationship
|
||||
@param relationshipName The name of the relationship for which the specified identifier is to be used.
|
||||
@return The identification predicate.
|
||||
*/
|
||||
- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier forRelationship:(NSString *)relationshipName;
|
||||
@property (nonatomic, copy) NSPredicate *identificationPredicate;
|
||||
|
||||
/**
|
||||
Returns the entity identifier for the relationship with the given name.
|
||||
|
||||
This method will return `nil` unless an entity identifier was specified via the `setEntityIdentifier:forRelationship:` method.
|
||||
|
||||
@param relationshipName The name of the relationship to retrieve the entity identifier for.
|
||||
@return The entity identifier for the specified relationship or `nil` if none was configured.
|
||||
*/
|
||||
- (RKEntityIdentifier *)entityIdentifierForRelationship:(NSString *)relationshipName;
|
||||
|
||||
///-------------------------------------------
|
||||
/// @name Configuring Relationship Connections
|
||||
@@ -120,7 +122,7 @@
|
||||
/**
|
||||
Returns the array of `RKConnectionDescripton` objects configured for connecting relationships during object mapping.
|
||||
*/
|
||||
@property (weak, nonatomic, readonly) NSArray *connections;
|
||||
@property (nonatomic, copy, readonly) NSArray *connections;
|
||||
|
||||
/**
|
||||
Adds a connection to the receiver.
|
||||
@@ -185,24 +187,43 @@
|
||||
*/
|
||||
- (id)defaultValueForAttribute:(NSString *)attributeName;
|
||||
|
||||
///----------------------------------------------
|
||||
/// @name Configuring Entity Identifier Inference
|
||||
///----------------------------------------------
|
||||
///--------------------------------------------------
|
||||
/// @name Configuring Entity Identification Inference
|
||||
///--------------------------------------------------
|
||||
|
||||
/**
|
||||
Enables or disabled entity identifier inference.
|
||||
Enables or disabled entity identification inference.
|
||||
|
||||
**Default:** `YES`
|
||||
|
||||
@param enabled A Boolean value indicating if entity identifier inference is to be performed.
|
||||
@param enabled A Boolean value indicating if entity identification inference is to be performed.
|
||||
*/
|
||||
+ (void)setEntityIdentifierInferenceEnabled:(BOOL)enabled;
|
||||
+ (void)setEntityIdentificationInferenceEnabled:(BOOL)enabled;
|
||||
|
||||
/**
|
||||
Returns a Boolean value that indicates is entity identifier inference has been enabled.
|
||||
Returns a Boolean value that indicates if entity identification inference has been enabled.
|
||||
|
||||
@return `YES` if entity identifier inference is enabled, else `NO`.
|
||||
@return `YES` if entity identification inference is enabled, else `NO`.
|
||||
*/
|
||||
+ (BOOL)isEntityIdentifierInferenceEnabled;
|
||||
+ (BOOL)isEntityIdentificationInferenceEnabled;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
The name of a key in the user info dictionary of a `NSEntityDescription` specifying the name or one or more attributes to be used to infer an entity identifier. The value of this string is 'RKEntityIdentificationAttributes'.
|
||||
*/
|
||||
extern NSString * const RKEntityIdentificationAttributesUserInfoKey;
|
||||
|
||||
///----------------
|
||||
/// @name Functions
|
||||
///----------------
|
||||
|
||||
/**
|
||||
Returns an array of attributes likely to be usable for identification purposes inferred from the given entity.
|
||||
|
||||
Please see the documentation accompanying the `RKEntityMapping` class for details about the inference rules.
|
||||
|
||||
@param entity The entity to infer identification from.
|
||||
@return An array containing identifying attributes inferred from the given entity or `nil` if none could be inferred.
|
||||
*/
|
||||
NSArray *RKIdentificationAttributesInferredFromEntity(NSEntityDescription *entity);
|
||||
|
||||
@@ -30,21 +30,86 @@
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent RKlcl_cRestKitCoreData
|
||||
|
||||
static BOOL entityIdentifierInferenceEnabled = YES;
|
||||
NSString * const RKEntityIdentificationAttributesUserInfoKey = @"RKEntityIdentificationAttributes";
|
||||
|
||||
static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
#pragma mark - Functions
|
||||
|
||||
static NSArray *RKEntityIdentificationAttributesFromUserInfoOfEntity(NSEntityDescription *entity)
|
||||
{
|
||||
if (! [RKEntityMapping isEntityIdentifierInferenceEnabled]) return;
|
||||
id userInfoValue = [entity userInfo][RKEntityIdentificationAttributesUserInfoKey];
|
||||
if (userInfoValue) {
|
||||
NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ];
|
||||
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]];
|
||||
[attributeNames enumerateObjectsUsingBlock:^(NSString *attributeName, NSUInteger idx, BOOL *stop) {
|
||||
if (! [attributeName isKindOfClass:[NSString class]]) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid value given in user info key '%@' of entity '%@': expected an `NSString` or `NSArray` of strings, instead got '%@' (%@)", RKEntityIdentificationAttributesUserInfoKey, [entity name], attributeName, [attributeName class]];
|
||||
}
|
||||
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeName];
|
||||
if (! attribute) {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid identifier attribute specified in user info key '%@' of entity '%@': no attribue was found with the name '%@'", RKEntityIdentificationAttributesUserInfoKey, [entity name], attributeName];
|
||||
}
|
||||
|
||||
[attributes addObject:attribute];
|
||||
}];
|
||||
return attributes;
|
||||
}
|
||||
|
||||
entityMapping.entityIdentifier = [RKEntityIdentifier inferredIdentifierForEntity:entityMapping.entity];
|
||||
[[entityMapping.entity relationshipsByName] enumerateKeysAndObjectsUsingBlock:^(NSString *relationshipName, NSRelationshipDescription *relationship, BOOL *stop) {
|
||||
RKEntityIdentifier *entityIdentififer = [RKEntityIdentifier inferredIdentifierForEntity:relationship.destinationEntity];
|
||||
if (entityIdentififer) {
|
||||
[entityMapping setEntityIdentifier:entityIdentififer forRelationship:relationshipName];
|
||||
}
|
||||
}];
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Given 'Human', returns 'humanID'; Given 'AmenityReview' returns 'amenityReviewID'
|
||||
static NSString *RKEntityIdentificationAttributeNameForEntity(NSEntityDescription *entity)
|
||||
{
|
||||
NSString *entityName = [entity name];
|
||||
NSString *lowerCasedFirstCharacter = [[entityName substringToIndex:1] lowercaseString];
|
||||
return [NSString stringWithFormat:@"%@%@ID", lowerCasedFirstCharacter, [entityName substringFromIndex:1]];
|
||||
}
|
||||
|
||||
static NSArray *RKEntityIdentificationAttributeNames()
|
||||
{
|
||||
return [NSArray arrayWithObjects:@"identifier", @"id", @"ID", @"URL", @"url", nil];
|
||||
}
|
||||
|
||||
static NSArray *RKArrayOfAttributesForEntityFromAttributesOrNames(NSEntityDescription *entity, NSArray *attributesOrNames)
|
||||
{
|
||||
NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributesOrNames count]];
|
||||
for (id attributeOrName in attributesOrNames) {
|
||||
if ([attributeOrName isKindOfClass:[NSAttributeDescription class]]) {
|
||||
if (! [[entity properties] containsObject:attributeOrName]) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute value '%@' given for entity identifer: not found in the '%@' entity", attributeOrName, [entity name]];
|
||||
[attributes addObject:attributeOrName];
|
||||
} else if ([attributeOrName isKindOfClass:[NSString class]]) {
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeOrName];
|
||||
if (!attribute) [NSException raise:NSInvalidArgumentException format:@"Invalid attribute '%@': no attribute was found for the given name in the '%@' entity.", attributeOrName, [entity name]];
|
||||
[attributes addObject:attribute];
|
||||
} else {
|
||||
[NSException raise:NSInvalidArgumentException format:@"Invalid value provided for entity identifier attribute: Acceptable values are either `NSAttributeDescription` or `NSString` objects."];
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
NSArray *RKIdentificationAttributesInferredFromEntity(NSEntityDescription *entity)
|
||||
{
|
||||
NSArray *attributes = RKEntityIdentificationAttributesFromUserInfoOfEntity(entity);
|
||||
if (attributes) {
|
||||
return RKArrayOfAttributesForEntityFromAttributesOrNames(entity, attributes);
|
||||
}
|
||||
|
||||
NSMutableArray *identifyingAttributes = [NSMutableArray arrayWithObject:RKEntityIdentificationAttributeNameForEntity(entity)];
|
||||
[identifyingAttributes addObjectsFromArray:RKEntityIdentificationAttributeNames()];
|
||||
for (NSString *attributeName in identifyingAttributes) {
|
||||
NSAttributeDescription *attribute = [entity attributesByName][attributeName];
|
||||
if (attribute) {
|
||||
return @[ attribute ];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static BOOL entityIdentificationInferenceEnabled = YES;
|
||||
|
||||
@interface RKObjectMapping (Private)
|
||||
- (NSString *)transformSourceKeyPath:(NSString *)keyPath;
|
||||
@end
|
||||
@@ -52,11 +117,12 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
@interface RKEntityMapping ()
|
||||
@property (nonatomic, weak, readwrite) Class objectClass;
|
||||
@property (nonatomic, strong) NSMutableArray *mutableConnections;
|
||||
@property (nonatomic, strong) NSMutableDictionary *relationshipNamesToEntityIdentifiers;
|
||||
@end
|
||||
|
||||
@implementation RKEntityMapping
|
||||
|
||||
@synthesize identificationAttributes = _identificationAttributes;
|
||||
|
||||
+ (id)mappingForClass:(Class)objectClass
|
||||
{
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
@@ -78,7 +144,7 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
self = [self initWithClass:objectClass];
|
||||
if (self) {
|
||||
self.entity = entity;
|
||||
RKInferIdentifiersForEntityMapping(self);
|
||||
if ([RKEntityMapping isEntityIdentificationInferenceEnabled]) self.identificationAttributes = RKIdentificationAttributesInferredFromEntity(entity);
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -89,7 +155,6 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
self = [super initWithClass:objectClass];
|
||||
if (self) {
|
||||
self.mutableConnections = [NSMutableArray array];
|
||||
self.relationshipNamesToEntityIdentifiers = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -98,7 +163,8 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
RKEntityMapping *copy = [super copyWithZone:zone];
|
||||
copy.entityIdentifier = [self.entityIdentifier copy];
|
||||
copy.identificationAttributes = self.identificationAttributes;
|
||||
copy.identificationPredicate = self.identificationPredicate;
|
||||
|
||||
for (RKConnectionDescription *connection in self.connections) {
|
||||
[copy addConnection:[connection copy]];
|
||||
@@ -107,6 +173,17 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (void)setIdentificationAttributes:(NSArray *)attributesOrNames
|
||||
{
|
||||
if (attributesOrNames && [attributesOrNames count] == 0) [NSException raise:NSInvalidArgumentException format:@"At least one attribute must be provided to identify managed objects"];
|
||||
_identificationAttributes = attributesOrNames ? RKArrayOfAttributesForEntityFromAttributesOrNames(self.entity, attributesOrNames) : nil;
|
||||
}
|
||||
|
||||
- (NSArray *)identificationAttributes
|
||||
{
|
||||
return _identificationAttributes;
|
||||
}
|
||||
|
||||
- (RKConnectionDescription *)connectionForRelationship:(id)relationshipOrName
|
||||
{
|
||||
NSAssert([relationshipOrName isKindOfClass:[NSString class]] || [relationshipOrName isKindOfClass:[NSRelationshipDescription class]], @"Relationship specifier must be a name or a relationship description");
|
||||
@@ -163,31 +240,6 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
[self.mutableConnections addObject:connection];
|
||||
}
|
||||
|
||||
- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier
|
||||
{
|
||||
NSAssert(entityIdentifier == nil || [entityIdentifier.entity isKindOfEntity:self.entity], @"Invalid entity identifier value: The identifier given is for the '%@' entity.", [entityIdentifier.entity name]);
|
||||
_entityIdentifier = entityIdentifier;
|
||||
}
|
||||
|
||||
- (void)setEntityIdentifier:(RKEntityIdentifier *)entityIdentifier forRelationship:(NSString *)relationshipName
|
||||
{
|
||||
NSRelationshipDescription *relationship = [self.entity relationshipsByName][relationshipName];
|
||||
NSAssert(relationship, @"Cannot set entity identififer for relationship '%@': no relationship found for that name.", relationshipName);
|
||||
NSAssert([[relationship destinationEntity] isKindOfEntity:entityIdentifier.entity], @"Cannot set entity identifier for relationship '%@': the given relationship identifier is for the '%@' entity, but the '%@' entity was expected.", relationshipName, [entityIdentifier.entity name], [[relationship destinationEntity] name]);
|
||||
self.relationshipNamesToEntityIdentifiers[relationshipName] = entityIdentifier;
|
||||
}
|
||||
|
||||
- (RKEntityIdentifier *)entityIdentifierForRelationship:(NSString *)relationshipName
|
||||
{
|
||||
RKEntityIdentifier *entityIdentifier = self.relationshipNamesToEntityIdentifiers[relationshipName];
|
||||
if (! entityIdentifier) {
|
||||
RKRelationshipMapping *relationshipMapping = [self propertyMappingsByDestinationKeyPath][relationshipName];
|
||||
entityIdentifier = [relationshipMapping.mapping isKindOfClass:[RKEntityIdentifier class]] ? [(RKEntityMapping *)relationshipMapping.mapping entityIdentifier] : nil;
|
||||
}
|
||||
|
||||
return entityIdentifier;
|
||||
}
|
||||
|
||||
- (id)defaultValueForAttribute:(NSString *)attributeName
|
||||
{
|
||||
NSAttributeDescription *desc = [[self.entity attributesByName] valueForKey:attributeName];
|
||||
@@ -204,14 +256,14 @@ static void RKInferIdentifiersForEntityMapping(RKEntityMapping *entityMapping)
|
||||
return propertyClass;
|
||||
}
|
||||
|
||||
+ (void)setEntityIdentifierInferenceEnabled:(BOOL)enabled
|
||||
+ (void)setEntityIdentificationInferenceEnabled:(BOOL)enabled
|
||||
{
|
||||
entityIdentifierInferenceEnabled = enabled;
|
||||
entityIdentificationInferenceEnabled = enabled;
|
||||
}
|
||||
|
||||
+ (BOOL)isEntityIdentifierInferenceEnabled
|
||||
+ (BOOL)isEntityIdentificationInferenceEnabled
|
||||
{
|
||||
return entityIdentifierInferenceEnabled;
|
||||
return entityIdentificationInferenceEnabled;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -38,7 +38,7 @@ NSArray *RKApplyNestingAttributeValueToMappings(NSString *attributeName, id valu
|
||||
// Return YES if the entity is identified by an attribute that acts as the nesting key in the source representation
|
||||
static BOOL RKEntityMappingIsIdentifiedByNestingAttribute(RKEntityMapping *entityMapping)
|
||||
{
|
||||
for (NSAttributeDescription *attribute in entityMapping.entityIdentifier.attributes) {
|
||||
for (NSAttributeDescription *attribute in [entityMapping identificationAttributes]) {
|
||||
RKAttributeMapping *attributeMapping = [[entityMapping propertyMappingsByDestinationKeyPath] objectForKey:[attribute name]];
|
||||
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
||||
return YES;
|
||||
@@ -49,10 +49,10 @@ static BOOL RKEntityMappingIsIdentifiedByNestingAttribute(RKEntityMapping *entit
|
||||
}
|
||||
|
||||
// We always need to map the dynamic nesting attribute first so that sub-key attribute mappings apply cleanly
|
||||
static NSArray *RKEntityIdentifierAttributesInMappingOrder(RKEntityMapping *entityMapping)
|
||||
static NSArray *RKEntityIdentificationAttributesInMappingOrder(RKEntityMapping *entityMapping)
|
||||
{
|
||||
NSMutableArray *orderedAttributes = [NSMutableArray arrayWithCapacity:[entityMapping.entityIdentifier.attributes count]];
|
||||
for (NSAttributeDescription *attribute in entityMapping.entityIdentifier.attributes) {
|
||||
NSMutableArray *orderedAttributes = [NSMutableArray arrayWithCapacity:[[entityMapping identificationAttributes] count]];
|
||||
for (NSAttributeDescription *attribute in [entityMapping identificationAttributes]) {
|
||||
RKAttributeMapping *attributeMapping = [[entityMapping propertyMappingsByDestinationKeyPath] objectForKey:[attribute name]];
|
||||
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
|
||||
// We want to map the nesting attribute first
|
||||
@@ -86,11 +86,11 @@ static RKAttributeMapping *RKAttributeMappingForNameInMappings(NSString *name, N
|
||||
/**
|
||||
This function is the workhorse for extracting entity identifier attributes from a dictionary representation. It supports type transformations, compound entity identifier attributes, and dynamic nesting keys within the representation.
|
||||
*/
|
||||
static NSDictionary *RKEntityIdentifierAttributesForEntityMappingWithRepresentation(RKEntityMapping *entityMapping, NSDictionary *representation)
|
||||
static NSDictionary *RKEntityIdentificationAttributesForEntityMappingWithRepresentation(RKEntityMapping *entityMapping, NSDictionary *representation)
|
||||
{
|
||||
RKDateToStringValueTransformer *dateToStringTransformer = [[RKDateToStringValueTransformer alloc] initWithDateToStringFormatter:entityMapping.preferredDateFormatter
|
||||
stringToDateFormatters:entityMapping.dateFormatters];
|
||||
NSArray *orderedAttributes = RKEntityIdentifierAttributesInMappingOrder(entityMapping);
|
||||
NSArray *orderedAttributes = RKEntityIdentificationAttributesInMappingOrder(entityMapping);
|
||||
BOOL containsNestingAttribute = RKEntityMappingIsIdentifiedByNestingAttribute(entityMapping);
|
||||
__block NSArray *attributeMappings = entityMapping.attributeMappings;
|
||||
if (containsNestingAttribute) RKLogDebug(@"Detected use of nested dictionary key as identifying attribute");
|
||||
@@ -151,7 +151,7 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
|
||||
}
|
||||
|
||||
RKEntityMapping *entityMapping = (RKEntityMapping *)mapping;
|
||||
NSDictionary *entityIdentifierAttributes = RKEntityIdentifierAttributesForEntityMappingWithRepresentation(entityMapping, representation);
|
||||
NSDictionary *entityIdentifierAttributes = RKEntityIdentificationAttributesForEntityMappingWithRepresentation(entityMapping, representation);
|
||||
if (! self.managedObjectCache) {
|
||||
RKLogWarning(@"Performing managed object mapping with a nil managed object cache:\n"
|
||||
"Unable to update existing object instances by primary key. Duplicate objects may be created.");
|
||||
@@ -164,7 +164,7 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
|
||||
NSArray *objects = [self.managedObjectCache managedObjectsWithEntity:entity
|
||||
attributeValues:entityIdentifierAttributes
|
||||
inManagedObjectContext:self.managedObjectContext];
|
||||
if (entityMapping.entityIdentifier.predicate) objects = [objects filteredArrayUsingPredicate:entityMapping.entityIdentifier.predicate];
|
||||
if (entityMapping.identificationPredicate) objects = [objects filteredArrayUsingPredicate:entityMapping.identificationPredicate];
|
||||
if ([objects count] > 0) {
|
||||
managedObject = objects[0];
|
||||
if ([objects count] > 1) RKLogWarning(@"Managed object cache returned %ld objects for the identifier configured for the '%@' entity, expected 1.", (long) [objects count], [entity name]);
|
||||
|
||||
Reference in New Issue
Block a user