Skip to content

Commit 80366af

Browse files
committed
Added Object Mapping block helpers to RKObjectManager and RKObjectMapping. 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 RestKit#238
1 parent 06e2f66 commit 80366af

File tree

7 files changed

+197
-6
lines changed

7 files changed

+197
-6
lines changed

Code/ObjectMapping/RKObjectManager.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,63 @@ typedef enum {
211211
*/
212212
- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate;
213213

214+
////////////////////////////////////////////////////////
215+
/// @name Block Configured Object Loaders
216+
217+
#if NS_BLOCKS_AVAILABLE
218+
219+
/**
220+
Configure and send an object loader after yielding it to a block for configuration. This allows for very succinct on-the-fly
221+
configuration of the request without obtaining an object reference via objectLoaderForObject: and then sending it yourself.
222+
223+
For example:
224+
225+
- (BOOL)changePassword:(NSString*)newPassword error:(NSError**)error {
226+
if ([self validatePassword:newPassword error:error]) {
227+
self.password = newPassword;
228+
[[RKObjectManager sharedManager] sendObject:self method:RKRequestMethodPOST delegate:self block:^(RKObjectLoader* loader) {
229+
loader.serializationMIMEType = RKMIMETypeJSON; // We want to send this request as JSON
230+
loader.targetObject = nil; // Map the results back onto a new object instead of self
231+
// Set up a custom serialization mapping to handle this request
232+
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
233+
[mapping mapAttributes:@"password", nil];
234+
}];
235+
}];
236+
}
237+
}
238+
*/
239+
- (RKObjectLoader*)sendObject:(id<NSObject>)object method:(RKRequestMethod)method delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
240+
241+
/**
242+
GET a remote object instance and yield the object loader to the block before sending
243+
244+
@see sendObject:method:delegate:block
245+
*/
246+
- (RKObjectLoader*)getObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
247+
248+
/**
249+
POST a remote object instance and yield the object loader to the block before sending
250+
251+
@see sendObject:method:delegate:block
252+
*/
253+
- (RKObjectLoader*)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
254+
255+
/**
256+
PUT a remote object instance and yield the object loader to the block before sending
257+
258+
@see sendObject:method:delegate:block
259+
*/
260+
- (RKObjectLoader*)putObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
261+
262+
/**
263+
DELETE a remote object instance and yield the object loader to the block before sending
264+
265+
@see sendObject:method:delegate:block
266+
*/
267+
- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block;
268+
269+
#endif
270+
214271
//////
215272

216273
/**

Code/ObjectMapping/RKObjectManager.m

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,31 @@ - (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoader
190190
return loader;
191191
}
192192

193+
#pragma mark - Block Configured Object Loaders
194+
195+
- (RKObjectLoader*)sendObject:(id<NSObject>)object method:(RKRequestMethod)method delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
196+
RKObjectLoader* loader = [self objectLoaderForObject:object method:method delegate:delegate];
197+
block(loader);
198+
[loader send];
199+
return loader;
200+
}
201+
202+
- (RKObjectLoader*)getObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
203+
return [self sendObject:object method:RKRequestMethodGET delegate:delegate block:block];
204+
}
205+
206+
- (RKObjectLoader*)postObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
207+
return [self sendObject:object method:RKRequestMethodPOST delegate:delegate block:block];
208+
}
209+
210+
- (RKObjectLoader*)putObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
211+
return [self sendObject:object method:RKRequestMethodPUT delegate:delegate block:block];
212+
}
213+
214+
- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate block:(void(^)(RKObjectLoader*))block {
215+
return [self sendObject:object method:RKRequestMethodDELETE delegate:delegate block:block];
216+
}
217+
193218
#pragma mark - Object Instance Loaders for Non-nested JSON
194219

195220
- (RKObjectLoader*)getObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {

Code/ObjectMapping/RKObjectMapping.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,53 @@ relationship. Relationships are processed using an object mapping as well.
123123
*/
124124
+ (id)mappingForClass:(Class)objectClass;
125125

126+
/**
127+
Returns an object mapping useful for configuring a serialization mapping. The object
128+
class is configured as NSMutableDictionary
129+
*/
130+
+ (id)serializationMapping;
131+
132+
#if NS_BLOCKS_AVAILABLE
133+
/**
134+
Returns an object mapping targeting the specified class. The RKObjectMapping instance will
135+
be yieled to the block so that you can perform on the fly configuration without having to
136+
obtain a reference variable for the mapping.
137+
138+
For example, consider we have a one-off request that will load a few attributes for our object.
139+
Using blocks, this is very succinct:
140+
141+
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
142+
loader.objectMapping = [RKObjectMapping mappingForClass:[Person class] block:^(RKObjectMapping* mapping) {
143+
[mapping mapAttributes:@"email", @"first_name", nil];
144+
}];
145+
}];
146+
*/
147+
+ (id)mappingForClass:(Class)objectClass block:(void(^)(RKObjectMapping*))block;
148+
149+
/**
150+
Returns serialization mapping for encoding a local object to a dictionary for transport. The RKObjectMapping instance will
151+
be yieled to the block so that you can perform on the fly configuration without having to
152+
obtain a reference variable for the mapping.
153+
154+
For example, consider we have a one-off request within which we want to post a subset of our object
155+
data. Using blocks, this is very succinct:
156+
157+
- (BOOL)changePassword:(NSString*)newPassword error:(NSError**)error {
158+
if ([self validatePassword:newPassword error:error]) {
159+
self.password = newPassword;
160+
[[RKObjectManager sharedManager] putObject:self delegate:self block:^(RKObjectLoader* loader) {
161+
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
162+
[mapping mapAttributes:@"password", nil];
163+
}];
164+
}];
165+
}
166+
}
167+
168+
Using the block forms we are able to quickly configure and send this request on the fly.
169+
*/
170+
+ (id)serializationMappingWithBlock:(void(^)(RKObjectMapping*))block;
171+
#endif
172+
126173
/**
127174
Add a configured attribute mapping to this object mapping
128175

Code/ObjectMapping/RKObjectMapping.m

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ + (id)mappingForClass:(Class)objectClass {
2828
return [mapping autorelease];
2929
}
3030

31+
+ (id)serializationMapping {
32+
return [self mappingForClass:[NSMutableDictionary class]];
33+
}
34+
35+
+ (id)mappingForClass:(Class)objectClass block:(void(^)(RKObjectMapping*))block {
36+
RKObjectMapping* mapping = [self mappingForClass:objectClass];
37+
block(mapping);
38+
return mapping;
39+
}
40+
41+
+ (id)serializationMappingWithBlock:(void(^)(RKObjectMapping*))block {
42+
RKObjectMapping* mapping = [self serializationMapping];
43+
block(mapping);
44+
return mapping;
45+
}
46+
47+
3148
- (id)init {
3249
self = [super init];
3350
if (self) {

Code/ObjectMapping/RKObjectRouter.m

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ - (void)dealloc {
2727

2828
- (void)routeClass:(Class)class toResourcePath:(NSString*)resourcePath forMethodName:(NSString*)methodName {
2929
NSString* className = NSStringFromClass(class);
30-
if (nil == [_routes objectForKey:className]) {
30+
if (nil == [_routes objectForKey:class]) {
3131
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
32-
[_routes setObject:dictionary forKey:className];
32+
[_routes setObject:dictionary forKey:class];
3333
}
3434

35-
NSMutableDictionary* classRoutes = [_routes objectForKey:className];
35+
NSMutableDictionary* classRoutes = [_routes objectForKey:class];
3636
if ([classRoutes objectForKey:methodName]) {
3737
[NSException raise:nil format:@"A route has already been registered for class '%@' and HTTP method '%@'", className, methodName];
3838
}
@@ -75,8 +75,26 @@ - (void)routeClass:(Class)class toResourcePath:(NSString*)resourcePath forMethod
7575

7676
- (NSString*)resourcePathForObject:(NSObject*)object method:(RKRequestMethod)method {
7777
NSString* methodName = [self HTTPVerbForMethod:method];
78-
NSString* className = NSStringFromClass([object class]);
79-
NSDictionary* classRoutes = [_routes objectForKey:className];
78+
NSString* className = NSStringFromClass([object class]);
79+
NSDictionary* classRoutes = nil;
80+
81+
// Check for exact matches
82+
for (Class possibleClass in _routes) {
83+
if ([object isMemberOfClass:possibleClass]) {
84+
classRoutes = [_routes objectForKey:possibleClass];
85+
break;
86+
}
87+
}
88+
89+
// Check for superclass matches
90+
if (! classRoutes) {
91+
for (Class possibleClass in _routes) {
92+
if ([object isKindOfClass:possibleClass]) {
93+
classRoutes = [_routes objectForKey:possibleClass];
94+
break;
95+
}
96+
}
97+
}
8098

8199
NSString* resourcePath = nil;
82100
if ((resourcePath = [classRoutes objectForKey:methodName])) {

Code/Support/Errors.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88

99
#import "Errors.h"
1010

11-
NSString* const RKRestKitErrorDomain = @"com.twotoasters.RestKit.ErrorDomain";
11+
NSString* const RKRestKitErrorDomain = @"org.restkit.RestKit.ErrorDomain";
1212

1313
NSString* const RKObjectMapperErrorObjectsKey = @"RKObjectMapperErrorObjectsKey";

Specs/ObjectMapping/RKObjectRouterSpec.m

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212
#import "RKHuman.h"
1313
#import "RKCat.h"
1414

15+
@interface RKSpecObject : NSObject
16+
@end
17+
@implementation RKSpecObject
18+
@end
19+
20+
@interface RKSpecSubclassedObject : RKSpecObject
21+
@end
22+
@implementation RKSpecSubclassedObject
23+
@end
24+
1525
@interface RKObjectRouterSpec : RKSpec {
1626
}
1727

@@ -64,6 +74,23 @@ -(void)itShouldReturnPathsRegisteredForTheClassAsAWhole {
6474
[expectThat(path) should:be(@"/HumanService.asp")];
6575
}
6676

77+
- (void)itShouldReturnPathsIfTheSuperclassIsRegistered {
78+
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
79+
[router routeClass:[RKSpecObject class] toResourcePath:@"/HumanService.asp"];
80+
NSString* path = [router resourcePathForObject:[RKSpecSubclassedObject new] method:RKRequestMethodGET];
81+
[expectThat(path) should:be(@"/HumanService.asp")];
82+
}
83+
84+
- (void)itShouldFavorExactMatcherOverSuperclassMatches {
85+
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
86+
[router routeClass:[RKSpecObject class] toResourcePath:@"/HumanService.asp"];
87+
[router routeClass:[RKSpecSubclassedObject class] toResourcePath:@"/SubclassedHumanService.asp"];
88+
NSString* path = [router resourcePathForObject:[RKSpecSubclassedObject new] method:RKRequestMethodGET];
89+
[expectThat(path) should:be(@"/SubclassedHumanService.asp")];
90+
path = [router resourcePathForObject:[RKSpecObject new] method:RKRequestMethodPOST];
91+
[expectThat(path) should:be(@"/HumanService.asp")];
92+
}
93+
6794
-(void)itShouldFavorSpecificMethodsWhenClassAndSpecificMethodsAreRegistered {
6895
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
6996
[router routeClass:[RKHuman class] toResourcePath:@"/HumanService.asp"];

0 commit comments

Comments
 (0)