Skip to content

Commit

Permalink
Added Object Mapping block helpers to RKObjectManager and RKObjectMap…
Browse files Browse the repository at this point in the history
…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
blakewatters committed Jul 24, 2011
1 parent 06e2f66 commit 80366af
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 6 deletions.
57 changes: 57 additions & 0 deletions Code/ObjectMapping/RKObjectManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

//////

/**
Expand Down
25 changes: 25 additions & 0 deletions Code/ObjectMapping/RKObjectManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions Code/ObjectMapping/RKObjectMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,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
Expand Down
17 changes: 17 additions & 0 deletions Code/ObjectMapping/RKObjectMapping.m
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
28 changes: 23 additions & 5 deletions Code/ObjectMapping/RKObjectRouter.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down Expand Up @@ -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])) {
Expand Down
2 changes: 1 addition & 1 deletion Code/Support/Errors.m
Original file line number Diff line number Diff line change
Expand Up @@ -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";
27 changes: 27 additions & 0 deletions Specs/ObjectMapping/RKObjectRouterSpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}

Expand Down Expand Up @@ -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"];
Expand Down

0 comments on commit 80366af

Please sign in to comment.