Permalink
Browse files

Added Object Mapping block helpers to RKObjectManager and RKObjectMap…

…ping. These enable you to perform ad-hoc object mapping very easily. Extended RKObjectRouter to match on superclasses if no specific route is found. This is helpful when using mocked objects with frameworks like Kiwi. fixes #238
  • Loading branch information...
1 parent 06e2f66 commit 80366afa84c48b2eb908eaef0767943dbef2579d @blakewatters blakewatters committed Jul 24, 2011
@@ -211,6 +211,63 @@ typedef enum {
*/
- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate;
+////////////////////////////////////////////////////////
+/// @name Block Configured Object Loaders
+
+#if NS_BLOCKS_AVAILABLE
+
+/**
+ Configure and send an object loader after yielding it to a block for configuration. This allows for very succinct on-the-fly
+ configuration of the request without obtaining an object reference via objectLoaderForObject: and then sending it yourself.
+
+ For example:
+
+ - (BOOL)changePassword:(NSString*)newPassword error:(NSError**)error {
+ if ([self validatePassword:newPassword error:error]) {
+ self.password = newPassword;
+ [[RKObjectManager sharedManager] sendObject:self method:RKRequestMethodPOST delegate:self block:^(RKObjectLoader* loader) {
+ loader.serializationMIMEType = RKMIMETypeJSON; // We want to send this request as JSON
+ loader.targetObject = nil; // Map the results back onto a new object instead of self
+ // Set up a custom serialization mapping to handle this request
+ loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
+ [mapping mapAttributes:@"password", nil];
+ }];
+ }];
+ }
+ }
+ */
+- (RKObjectLoader*)sendObject:(id<NSObject>)object method:(RKRequestMethod)method delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
+
+/**
+ GET a remote object instance and yield the object loader to the block before sending
+
+ @see sendObject:method:delegate:block
+ */
+- (RKObjectLoader*)getObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
+
+/**
+ POST a remote object instance and yield the object loader to the block before sending
+
+ @see sendObject:method:delegate:block
+ */
+- (RKObjectLoader*)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
+
+/**
+ PUT a remote object instance and yield the object loader to the block before sending
+
+ @see sendObject:method:delegate:block
+ */
+- (RKObjectLoader*)putObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
+
+/**
+ DELETE a remote object instance and yield the object loader to the block before sending
+
+ @see sendObject:method:delegate:block
+ */
+- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
+
+#endif
+
//////
/**
@@ -190,6 +190,31 @@ - (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoader
return loader;
}
+#pragma mark - Block Configured Object Loaders
+
+- (RKObjectLoader*)sendObject:(id<NSObject>)object method:(RKRequestMethod)method delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
+ RKObjectLoader* loader = [self objectLoaderForObject:object method:method delegate:delegate];
+ block(loader);
+ [loader send];
+ return loader;
+}
+
+- (RKObjectLoader*)getObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
+ return [self sendObject:object method:RKRequestMethodGET delegate:delegate block:block];
+}
+
+- (RKObjectLoader*)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
+ return [self sendObject:object method:RKRequestMethodPOST delegate:delegate block:block];
+}
+
+- (RKObjectLoader*)putObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
+ return [self sendObject:object method:RKRequestMethodPUT delegate:delegate block:block];
+}
+
+- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
+ return [self sendObject:object method:RKRequestMethodDELETE delegate:delegate block:block];
+}
+
#pragma mark - Object Instance Loaders for Non-nested JSON
- (RKObjectLoader*)getObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
@@ -124,6 +124,53 @@ relationship. Relationships are processed using an object mapping as well.
+ (id)mappingForClass:(Class)objectClass;
/**
+ Returns an object mapping useful for configuring a serialization mapping. The object
+ class is configured as NSMutableDictionary
+ */
++ (id)serializationMapping;
+
+#if NS_BLOCKS_AVAILABLE
+/**
+ Returns an object mapping targeting the specified class. The RKObjectMapping instance will
+ be yieled to the block so that you can perform on the fly configuration without having to
+ obtain a reference variable for the mapping.
+
+ For example, consider we have a one-off request that will load a few attributes for our object.
+ Using blocks, this is very succinct:
+
+ [[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
+ loader.objectMapping = [RKObjectMapping mappingForClass:[Person class] block:^(RKObjectMapping* mapping) {
+ [mapping mapAttributes:@"email", @"first_name", nil];
+ }];
+ }];
+ */
++ (id)mappingForClass:(Class)objectClass block:(void(^)(RKObjectMapping*))block;
+
+/**
+ Returns serialization mapping for encoding a local object to a dictionary for transport. The RKObjectMapping instance will
+ be yieled to the block so that you can perform on the fly configuration without having to
+ obtain a reference variable for the mapping.
+
+ For example, consider we have a one-off request within which we want to post a subset of our object
+ data. Using blocks, this is very succinct:
+
+ - (BOOL)changePassword:(NSString*)newPassword error:(NSError**)error {
+ if ([self validatePassword:newPassword error:error]) {
+ self.password = newPassword;
+ [[RKObjectManager sharedManager] putObject:self delegate:self block:^(RKObjectLoader* loader) {
+ loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
+ [mapping mapAttributes:@"password", nil];
+ }];
+ }];
+ }
+ }
+
+ Using the block forms we are able to quickly configure and send this request on the fly.
+ */
++ (id)serializationMappingWithBlock:(void(^)(RKObjectMapping*))block;
+#endif
+
+/**
Add a configured attribute mapping to this object mapping
@see RKObjectAttributeMapping
@@ -28,6 +28,23 @@ + (id)mappingForClass:(Class)objectClass {
return [mapping autorelease];
}
++ (id)serializationMapping {
+ return [self mappingForClass:[NSMutableDictionary class]];
+}
+
++ (id)mappingForClass:(Class)objectClass block:(void(^)(RKObjectMapping*))block {
+ RKObjectMapping* mapping = [self mappingForClass:objectClass];
+ block(mapping);
+ return mapping;
+}
+
++ (id)serializationMappingWithBlock:(void(^)(RKObjectMapping*))block {
+ RKObjectMapping* mapping = [self serializationMapping];
+ block(mapping);
+ return mapping;
+}
+
+
- (id)init {
self = [super init];
if (self) {
@@ -27,12 +27,12 @@ - (void)dealloc {
- (void)routeClass:(Class)class toResourcePath:(NSString*)resourcePath forMethodName:(NSString*)methodName {
NSString* className = NSStringFromClass(class);
- if (nil == [_routes objectForKey:className]) {
+ if (nil == [_routes objectForKey:class]) {
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
- [_routes setObject:dictionary forKey:className];
+ [_routes setObject:dictionary forKey:class];
}
- NSMutableDictionary* classRoutes = [_routes objectForKey:className];
+ NSMutableDictionary* classRoutes = [_routes objectForKey:class];
if ([classRoutes objectForKey:methodName]) {
[NSException raise:nil format:@"A route has already been registered for class '%@' and HTTP method '%@'", className, methodName];
}
@@ -75,8 +75,26 @@ - (void)routeClass:(Class)class toResourcePath:(NSString*)resourcePath forMethod
- (NSString*)resourcePathForObject:(NSObject*)object method:(RKRequestMethod)method {
NSString* methodName = [self HTTPVerbForMethod:method];
- NSString* className = NSStringFromClass([object class]);
- NSDictionary* classRoutes = [_routes objectForKey:className];
+ NSString* className = NSStringFromClass([object class]);
+ NSDictionary* classRoutes = nil;
+
+ // Check for exact matches
+ for (Class possibleClass in _routes) {
+ if ([object isMemberOfClass:possibleClass]) {
+ classRoutes = [_routes objectForKey:possibleClass];
+ break;
+ }
+ }
+
+ // Check for superclass matches
+ if (! classRoutes) {
+ for (Class possibleClass in _routes) {
+ if ([object isKindOfClass:possibleClass]) {
+ classRoutes = [_routes objectForKey:possibleClass];
+ break;
+ }
+ }
+ }
NSString* resourcePath = nil;
if ((resourcePath = [classRoutes objectForKey:methodName])) {
@@ -8,6 +8,6 @@
#import "Errors.h"
-NSString* const RKRestKitErrorDomain = @"com.twotoasters.RestKit.ErrorDomain";
+NSString* const RKRestKitErrorDomain = @"org.restkit.RestKit.ErrorDomain";
NSString* const RKObjectMapperErrorObjectsKey = @"RKObjectMapperErrorObjectsKey";
@@ -12,6 +12,16 @@
#import "RKHuman.h"
#import "RKCat.h"
+@interface RKSpecObject : NSObject
+@end
+@implementation RKSpecObject
+@end
+
+@interface RKSpecSubclassedObject : RKSpecObject
+@end
+@implementation RKSpecSubclassedObject
+@end
+
@interface RKObjectRouterSpec : RKSpec {
}
@@ -64,6 +74,23 @@ -(void)itShouldReturnPathsRegisteredForTheClassAsAWhole {
[expectThat(path) should:be(@"/HumanService.asp")];
}
+- (void)itShouldReturnPathsIfTheSuperclassIsRegistered {
+ RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
+ [router routeClass:[RKSpecObject class] toResourcePath:@"/HumanService.asp"];
+ NSString* path = [router resourcePathForObject:[RKSpecSubclassedObject new] method:RKRequestMethodGET];
+ [expectThat(path) should:be(@"/HumanService.asp")];
+}
+
+- (void)itShouldFavorExactMatcherOverSuperclassMatches {
+ RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
+ [router routeClass:[RKSpecObject class] toResourcePath:@"/HumanService.asp"];
+ [router routeClass:[RKSpecSubclassedObject class] toResourcePath:@"/SubclassedHumanService.asp"];
+ NSString* path = [router resourcePathForObject:[RKSpecSubclassedObject new] method:RKRequestMethodGET];
+ [expectThat(path) should:be(@"/SubclassedHumanService.asp")];
+ path = [router resourcePathForObject:[RKSpecObject new] method:RKRequestMethodPOST];
+ [expectThat(path) should:be(@"/HumanService.asp")];
+}
+
-(void)itShouldFavorSpecificMethodsWhenClassAndSpecificMethodsAreRegistered {
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
[router routeClass:[RKHuman class] toResourcePath:@"/HumanService.asp"];

0 comments on commit 80366af

Please sign in to comment.