Permalink
Browse files

Updated Object Mapping documentation, added support for inferring obj…

…ect mappings based off of the type of the object used in postObject:, putObject:, etc. Made KVC validation optional.
  • Loading branch information...
1 parent 80366af commit 570b13ca07f1eb28a52922dd6d67a104fa9613e5 @blakewatters blakewatters committed Jul 24, 2011
@@ -100,6 +100,7 @@ typedef enum {
RKObjectManagerOnlineState _onlineState;
RKObjectMappingProvider* _mappingProvider;
NSString* _serializationMIMEType;
+ BOOL _inferMappingsFromObjectTypes;
}
/// @name Configuring the Shared Manager Instance
@@ -167,6 +168,14 @@ typedef enum {
*/
@property (nonatomic, assign) NSString* acceptMIMEType;
+/**
+ When YES, RestKit will auto-select the appropriate object mapping for a particular object
+ passed through getObject:, postObject:, putObject:, and deleteObject:.
+
+ Default: YES
+ */
+@property (nonatomic, assign) BOOL inferMappingsFromObjectTypes;
+
////////////////////////////////////////////////////////
/// @name Registered Object Loaders
@@ -30,6 +30,7 @@ @implementation RKObjectManager
@synthesize router = _router;
@synthesize mappingProvider = _mappingProvider;
@synthesize serializationMIMEType = _serializationMIMEType;
+@synthesize inferMappingsFromObjectTypes = _inferMappingsFromObjectTypes;
- (id)initWithBaseURL:(NSString*)baseURL {
self = [super init];
@@ -38,6 +39,7 @@ - (id)initWithBaseURL:(NSString*)baseURL {
_router = [RKObjectRouter new];
_client = [[RKClient clientWithBaseURL:baseURL] retain];
_onlineState = RKObjectManagerOnlineStateUndetermined;
+ _inferMappingsFromObjectTypes = YES;
self.acceptMIMEType = RKMIMETypeJSON;
self.serializationMIMEType = RKMIMETypeFormURLEncoded;
@@ -162,6 +164,12 @@ - (RKObjectLoader*)objectLoaderForObject:(id<NSObject>)object method:(RKRequestM
loader.targetObject = object;
loader.serializationMIMEType = self.serializationMIMEType;
loader.serializationMapping = [self.mappingProvider serializationMappingForClass:[object class]];
+
+ if (self.inferMappingsFromObjectTypes) {
+ RKObjectMapping* objectMapping = [self.mappingProvider objectMappingForClass:[object class]];
+ RKLogDebug(@"Auto-selected object mapping %@ for object of type %@", objectMapping, NSStringFromClass([object class]));
+ loader.objectMapping = objectMapping;
+ }
return loader;
}
@@ -38,6 +38,7 @@ relationship. Relationships are processed using an object mapping as well.
BOOL _setDefaultValueForMissingAttributes;
BOOL _setNilForMissingRelationships;
BOOL _forceCollectionMapping;
+ BOOL _performKeyValueValidation;
}
/**
@@ -88,6 +89,14 @@ relationship. Relationships are processed using an object mapping as well.
@property (nonatomic, assign) BOOL setNilForMissingRelationships;
/**
+ When YES, RestKit will invoke key-value validation at object mapping time.
+
+ **Default**: YES
+ @see validateValue:forKey:error:
+ */
+@property (nonatomic, assign) BOOL performKeyValueValidation;
+
+/**
Forces the mapper to treat the mapped keyPath as a collection even if it does not
return an array or a set of objects. This permits mapping where a dictionary identifies
a collection of objects.
@@ -21,6 +21,7 @@ @implementation RKObjectMapping
@synthesize setDefaultValueForMissingAttributes = _setDefaultValueForMissingAttributes;
@synthesize setNilForMissingRelationships = _setNilForMissingRelationships;
@synthesize forceCollectionMapping = _forceCollectionMapping;
+@synthesize performKeyValueValidation = _performKeyValueValidation;
+ (id)mappingForClass:(Class)objectClass {
RKObjectMapping* mapping = [self new];
@@ -53,6 +54,7 @@ - (id)init {
self.setDefaultValueForMissingAttributes = NO;
self.setNilForMissingRelationships = NO;
self.forceCollectionMapping = NO;
+ self.performKeyValueValidation = YES;
}
return self;
@@ -160,9 +160,9 @@ - (BOOL)isValue:(id)sourceValue equalToValue:(id)destinationValue {
}
- (BOOL)validateValue:(id)value atKeyPath:(NSString*)keyPath {
- BOOL success = YES;
+ BOOL success = YES;
- if ([self.destinationObject respondsToSelector:@selector(validateValue:forKey:error:)]) {
+ if (self.objectMapping.performKeyValueValidation && [self.destinationObject respondsToSelector:@selector(validateValue:forKey:error:)]) {
success = [self.destinationObject validateValue:&value forKey:keyPath error:&_validationError];
if (!success) {
if (_validationError) {
@@ -254,6 +254,10 @@ - (BOOL)applyAttributeMappings {
// If we have a nesting substitution value, we have alread
BOOL appliedMappings = (_nestedAttributeSubstitution != nil);
+ if (!self.objectMapping.performKeyValueValidation) {
+ RKLogDebug(@"Key-value validation is disabled for mapping, skipping...");
+ }
+
for (RKObjectAttributeMapping* attributeMapping in [self attributeMappings]) {
if ([attributeMapping.sourceKeyPath isEqualToString:RKObjectMappingNestingAttributeKeyName]) {
RKLogTrace(@"Skipping attribute mapping for special keyPath '%@'", RKObjectMappingNestingAttributeKeyName);
@@ -52,6 +52,7 @@ - (NSDictionary*)objectMappingsByKeyPath {
- (void)registerMapping:(RKObjectMapping*)objectMapping withRootKeyPath:(NSString*)keyPath {
// TODO: Should generate logs
+ objectMapping.rootKeyPath = keyPath;
[self setObjectMapping:objectMapping forKeyPath:keyPath];
RKObjectMapping* inverseMapping = [objectMapping inverseMapping];
inverseMapping.rootKeyPath = keyPath;
@@ -75,7 +76,8 @@ - (NSArray*)objectMappingsForClass:(Class)theClass {
}
- (RKObjectMapping*)objectMappingForClass:(Class)theClass {
- return [[self objectMappingsForClass:theClass] objectAtIndex:0];
+ NSArray* objectMappings = [self objectMappingsForClass:theClass];
+ return ([objectMappings count] > 0) ? [objectMappings objectAtIndex:0] : nil;
}
@end
@@ -1,187 +0,0 @@
-# Object Mapping
-
-This document details the design of object mapping in RestKit as of version 0.9.3
-
-## The Object Mapper is Designed to...
-- Take parsing responsibilities out of the mapper entirely
-- Support arbitrarily complex mapping operations
-- Enable full transparency and insight into the mapping operation
-- Provide a clean, clear mapping API that is easy to work on and extend
-- Reduce the API foot-print of object mapping
-- Enable mapping onto vanilla NSObject and NSManagedObject classes
-- Enable mapping from model objects back into dictionaries and arrays (support for serialization)
-- Provide simple hooks for customizing the mapping decisions
-- Fully embrace key-value coding and organize the API around keyPaths
-- Support mapping multiple keyPaths from a dictionary and returning a dictionary instead of requiring encapsulation
-via relationships
-
-## Discussion
-
-Object mapping is the process RestKit uses to transform objects between representations. Object mapping
-leverages key-value coding conventions to determine how to map keyPaths between object instances and
-attributes. The process is composed of three steps:
-
-1. Identification: An `RKObjectMapper` is initialized with an arbitrary collection of key-value coding
-compliant data, a keyPath the object resides at (can be nil), and a mapping provider. The mapping provider
-supplies the mapper with mappable keyPaths
-1. Processing of Mappable Objects: If a dictionary or array is found and a corresponding object mapping is
-available for the keyPath, an `RKObjectMappingOperation` is created to process the data.
-1. Attribute & Relationship Mapping: Each attribute or relationship mapping within the object mapping definition
-is evaluated against the mappable data and the result is set on the target object.
-
-## Class Hierarchy
-- **RKObjectManager** - The external client interface for performing object mapping operations on resources
-loaded from the network. The object manager is responsible for creating object loaders and brokering interactions
-between the application and object mapping subsystem.
-- **RKObjectLoader** - A subclass of RKRequest that sends an HTTP request and performs an object mapping operation
-on the resulting payload. Responsible for parsing the payload appropriately and initializing an `RKObjectMapper` to handle the mapping.
-- **RKObjectMappingProvider** - Responsible for providing keyPaths and RKObjectMapping objects to instances of `RKObjectMapper`.
-An instance of the mapping provider is available via the `mappingProvider` property of `RKObjectManager`. This mapping provider
-is automatically assigned to all `RKObjectLoader` instances instantiated through the object manager.
-- **RKObjectMapping** - A definition of an object mapping for a particular class. Contains a collection of attribute mappings
-defining how attributes in a particular mappable object should be mapped onto the target class. Also contains relationship mappings
-specifying how to map nested object structures into one-to-one or one-to-many relationships. Object mappings are registered with the
-mapping provider to define rules for mapping and serializing objects.
-- **RKObjectAttributeMapping** - Defines a mapping from a source keyPath to a destination keyPath within an object mapping
-definition. For example, defines a rule that the NSString attribute at the `created_at` keyPath maps to the NSString property at
-the `createdAt` keyPath on the destination object.
-- **RKObjectRelationshipMapping** - A subclass of `RKObjectAttributeMapping` that defines a mapping to a related mappable object.
-Includes an objectMapping property defining the rules for mapping the related object. Used for transforming nested arrays and dictionaries into
-related objects.
-- **RKObjectMapper** - The interface for performing object mapping on mappable data. The mapper evaluates the type of the object
-and obtains the appropriate object mapping from an `RKObjectMappingProvider` and applies it by creating instances of `RKObjectMappingOperation`.
-- **RKObjectMappingOperation** - Responsible for applying an object mapping to a particular mappable dictionary. Evaluates the attribute mappings
-contained in the `RKObjectMapping` against the mappable dictionary and assigns the results to the target object. Recursively creates child mapping
-operations for all relationships and continues on until a full object graph has been constructed according to the mapping rules.
-- **RKObjectMappingResult** - When `RKObjectMapper` has finished its work, it will either return nil to indicate an unrecoverable error occurred or
-will return an instance of `RKObjectMappingResult`. The mapping result enables you to coerce the mapped results into the format you wish to work with.
-The currently available result coercions are:
- 1. `asObject` - Return the result as a single object. Useful when you know you have mapped a single object back (i.e. used `postObject`).
- 1. `asCollection` - Return the result as a collection of mapped objects. This will take all the mapped keyPaths and combine all mapped objects
- under those keyPaths and return it as a single array of objects.
- 1. `asDictionary` - Return the result as a dictionary of keyPaths and mapped object pairs. Useful when you want to identify your results by keyPath.
- 1. `asError` - Return the result as an NSError. The error is constructed by coercing the result into a collection, then calling `description` on all
- mapped objects to turn them into a string. The collection of error message strings are then joined together with the ", " delimiter to construct
- the localizedDescription for the error. The raw error objects the error was mapped from is available on the userInfo of the NSError instance. This
- is useful when you encountered a server side error and want to coerce the mapping results into an NSError. This is how `objectLoader:didFailWithError`
- returns server side error messages to you.
-- **RKObjectRouter** - Responsible for generating resource paths for accessing remote representations of objects. Capable of generating a resource
-path by interpolating property values into a string. For example, a path of "/articles/(articleID)" when applied to an Article object with a `articleID` property
-with the value 12345, would generate "/articles/12345". The object router is used to generate resource paths when getObject, postObject, putObject and deleteObject
-are invoked.
-- **RKErrorMessage** - A simple class providing for the mapping of server-side error messages back to NSError objects. Contains a single `errorMessage` property. When an
-RKObjectManager is initialized, object mappings from the "error" and "errors" keyPaths to instances of RKErrorMessage are registered with the mapping provider. This
-provides out of the box mapping support for simple error messages. The mappings can be removed or replaced by the developer to handle more advanced error return values,
-such as containing a server side error code or other metadata.
-- **RKObjectPropertyInspector** - An internally used singleton object responsible for cacheing the properties and types of a target class. This is used during object mapping
-to transform values at mapping time based on the source and destination types.
-- **RKObjectSerializer** - Responsible for taking Cocoa objects are serializing them for transport via HTTP to a remote server. The serializer takes an instance of an object
-and maps it back into an NSDictionary representation by creating an `RKObjectMappingOperation`. During the mapping process, certain types are transformed into serializable
-representation (i.e. NSDate & NSDecimalNumber objects are coerced into NSString instances). Once mapping is complete, the NSDictionary instance is encoded into a wire format
-and returned as an `RKRequestSerializable` object. The serializer is invoked for you automatically when `postObject` and `putObject` are invoked on the object manager. The
-appropriate mapping is selected by consulting the `objectMappingForClass:` method of the `RKObjectMappingProvider` instance configured on the object manager. Currently serialization to form encoded and JSON is supported.
-- **RKParserRegistry** - Responsible for maintaining the association between MIME Types and the `RKParser` object responsible for handling it. There is a singleton
-sharedRegistry instance that is automatically configured at library initialization time to handle the application/json and application/xml MIME types. Runtime detection
-of the parser classes is utilized to configure the registry appropriately. For JSON, autoconfiguration searches for JSONKit, then YAJL, and then SBJSON.
-
-## Tasks
-
-### Configuring an Object Mapping
-```objc
- // In this use-case Article is a vanilla NSObject with properties
- RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[Article class]];
-
- // Add an attribute mapping to the object mapping directly
- RKObjectAttributeMapping* titleMapping = [RKObjectAttributeMapping mappingFromKeyPath:@"title" toKeyPath:@"title"];
- [mapping addAttributeMapping:titleMapping];
-
- // Configure attribute mappings using helper methods
- [mapping mapAttributes:@"title", @"body", nil]; // Define mappings where the keyPath and target attribute have the same name
- [mapping mapKeyPath:@"created_at" toAttribute:@"createdAt"]; // Map a differing keyPath and attribute
- [mapping mapKeyPathsToAttributes:@"some.keyPath", @"targetAttribute1", @"another.keyPath", @"targetAttribute2", nil];
-
- // Configure relationship mappings
- RKObjectMapping* commentMapping = [[RKObjectManager sharedManager] objectMappingForClass:[Comment class]];
- // Direct configuration of instances
- RKObjectRelationshipMapping* articleCommentsMapping = [RKObjectRelationshipMapping mappingFromKeyPath:@"comments" toKeyPath:@"comments" objectMapping:commentMapping];
- [mapping addRelationshipMapping:articleCommentsMapping];
-
- // Configuration using helper methods
- [mapping mapRelationship:@"comments" withObjectMapping:commentMapping];
- [mapping hasMany:@"comments" withObjectMapping:commentMapping];
- [mapping belongsTo:@"user" withObjectMapping:userMapping];
-
- // Register the mapping with the object manager
- [objectManager.mappingProvider setMapping:mapping forKeyPath:@"article"];
-```
-
-### Configuring a Core Data Object Mapping
-TODO - Finish up
-```objc
- // TODO: Not yet implemented.
- [mapping belongsTo:@"author" withObjectMapping:[User objectMapping] andPrimaryKey:@"author_id"];
-```
-
-### Loading Using KeyPath Mapping Lookup
-```objc
- RKObjectLoader* loader = [RKObjectManager loadObjectsAtResourcePath:@"/objects" delegate:self];
- // The object mapper will try to determine the mappings by examining keyPaths in the loaded payload
-```
-
-### Load using an explicit mapping
-```objc
- RKObjectMapping* articleMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Article class]];
- RKObjectLoader* loader = [RKObjectManager loadObjectsAtResourcePath:@"/objects" withMapping:articleMapping delegate:self];
- loader.mappingDelegate = self;
-```
-
-### Configuring the Serialization Format
-```objc
- // Serialize to Form Encoded
- [RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeFormURLEncoded;
-
- // Serialize to JSON
- [RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeJSON;
-```
-
-### Serialization from an object to a Dictionary
-This is handled for you when using postObject and putObject, presented here for reference
-
-```objc
- RKUser* user = [User new];
- user.firstName = @"Blake";
- user.lastName = @"Watters";
-
- RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[NSDictionary class]];
- [mapping mapAttributes:@"firstName", @"lastName", nil];
-
- RKObjectSerializer* serializer = [RKObjectSerializer serializerWithObject:object mapping:serializationMapping];
- NSError* error = nil;
- id serialization = [serializer serializationForMIMEType:RKMIMETypeJSON error:&error];
-```
-
-### Performing a Mapping
-This is handled for you when using loadObjectAtResourcePath:, presented here for reference:
-
-```objc
- NSString* JSONString = @"{ \"name\": \"The name\", \"number\": 12345}";
- NSString* MIMEType = @"application/json";
- NSError* error = nil;
- id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:MIMEType];
- id parsedData = [parser objectFromString:JSONString error:error];
- if (parsedData == nil && error) {
- // Parser error...
- }
-
- RKObjectMappingProvider* mappingProvider = [RKObjectManager sharedManager].mappingProvider;
- RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
- RKObjectMappingResult* result = [mapper performMapping];
- if (result) {
- // Yay! Mapping finished successfully
- }
-```
-
-### Registering a Parser
-```objc
- [[RKParserRegistry sharedRegistry] setParserClass:[RKJSONParserJSONKit class] forMIMEType:RKMIMETypeJSON];
-```
Oops, something went wrong.

0 comments on commit 570b13c

Please sign in to comment.