Introduced mapping contexts support.

Extended RKObjectMappingProvider to store collections of object mappings for different use cases. The framework
now stores object mappings, serialization mappings, an error mapping and a pagination mapping using the context
support. Contexts can be added to the provider via method calls or extension via a category.
This commit is contained in:
Blake Watters
2012-01-15 22:03:40 -05:00
parent 31dcb31188
commit 6abbb34ef0
14 changed files with 631 additions and 175 deletions

View File

@@ -151,7 +151,7 @@
#pragma mark - Response Object Mapping
- (RKObjectMappingResult*)mapResponseWithMappingProvider:(RKObjectMappingProvider*)mappingProvider toObject:(id)targetObject error:(NSError**)error {
- (RKObjectMappingResult*)mapResponseWithMappingProvider:(RKObjectMappingProvider*)mappingProvider toObject:(id)targetObject inContext:(RKObjectMappingProviderContext)context error:(NSError**)error {
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:self.response.MIMEType];
NSAssert1(parser, @"Cannot perform object load without a parser for MIME Type '%@'", self.response.MIMEType);
@@ -182,6 +182,7 @@
RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
mapper.targetObject = targetObject;
mapper.delegate = self;
mapper.context = context;
RKObjectMappingResult* result = [mapper performMapping];
// Log any mapping errors
@@ -205,15 +206,15 @@
RKObjectMappingProvider* mappingProvider;
if (self.objectMapping) {
NSString* rootKeyPath = self.objectMapping.rootKeyPath ? self.objectMapping.rootKeyPath : @"";
RKLogDebug(@"Found directly configured object mapping, creating temporary mapping provider for keyPath %@", rootKeyPath);
mappingProvider = [[RKObjectMappingProvider new] autorelease];
RKLogDebug(@"Found directly configured object mapping, creating temporary mapping provider for keyPath %@", rootKeyPath);
mappingProvider = [RKObjectMappingProvider mappingProvider];
[mappingProvider setMapping:self.objectMapping forKeyPath:rootKeyPath];
} else {
RKLogDebug(@"No object mapping provider, using mapping provider from parent object manager to perform KVC mapping");
mappingProvider = self.mappingProvider;
}
return [self mapResponseWithMappingProvider:mappingProvider toObject:self.targetObject error:error];
return [self mapResponseWithMappingProvider:mappingProvider toObject:self.targetObject inContext:RKObjectMappingProviderContextObjectsByKeyPath error:error];
}
@@ -283,8 +284,8 @@
- (void)handleResponseError {
// Since we are mapping what we know to be an error response, we don't want to map the result back onto our
// target object
NSError* error = nil;
RKObjectMappingResult* result = [self mapResponseWithMappingProvider:self.mappingProvider toObject:nil error:&error];
NSError *error = nil;
RKObjectMappingResult *result = [self mapResponseWithMappingProvider:self.mappingProvider toObject:nil inContext:RKObjectMappingProviderContextErrors error:&error];
if (result) {
error = [result asError];
} else {

View File

@@ -58,9 +58,9 @@ static RKObjectManager* sharedManager = nil;
// Setup default error message mappings
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
errorMapping.rootKeyPath = @"errors";
[errorMapping mapKeyPath:@"" toAttribute:@"errorMessage"];
[_mappingProvider setMapping:errorMapping forKeyPath:@"error"];
[_mappingProvider setMapping:errorMapping forKeyPath:@"errors"];
_mappingProvider.errorMapping = errorMapping;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged:)

View File

@@ -48,17 +48,15 @@
@end
@interface RKObjectMapper : NSObject {
id _sourceObject;
id _targetObject;
RKObjectMappingProvider* _mappingProvider;
id<RKObjectMapperDelegate> _delegate;
NSMutableArray* _errors;
RKMappingOperationQueue *_operationQueue;
@protected
RKMappingOperationQueue *operationQueue;
NSMutableArray* errors;
}
@property (nonatomic, readonly) id sourceObject;
@property (nonatomic, assign) id targetObject;
@property (nonatomic, readonly) RKObjectMappingProvider* mappingProvider;
@property (nonatomic, assign) RKObjectMappingProviderContext context;
@property (nonatomic, assign) id<RKObjectMapperDelegate> delegate;
@property (nonatomic, readonly) NSArray* errors;

View File

@@ -28,44 +28,50 @@
@implementation RKObjectMapper
@synthesize sourceObject = _sourceObject;
@synthesize targetObject = _targetObject;
@synthesize delegate =_delegate;
@synthesize mappingProvider = _mappingProvider;
@synthesize errors = _errors;
@synthesize sourceObject;
@synthesize targetObject;
@synthesize delegate;
@synthesize mappingProvider;
@synthesize errors;
@synthesize context;
+ (id)mapperWithObject:(id)object mappingProvider:(RKObjectMappingProvider*)mappingProvider {
return [[[self alloc] initWithObject:object mappingProvider:mappingProvider] autorelease];
+ (id)mapperWithObject:(id)object mappingProvider:(RKObjectMappingProvider *)theMappingProvider {
return [[[self alloc] initWithObject:object mappingProvider:theMappingProvider] autorelease];
}
- (id)initWithObject:(id)object mappingProvider:(RKObjectMappingProvider*)mappingProvider {
- (id)initWithObject:(id)object mappingProvider:(RKObjectMappingProvider *)theMappingProvider {
self = [super init];
if (self) {
_sourceObject = [object retain];
_mappingProvider = mappingProvider;
_errors = [NSMutableArray new];
_operationQueue = [RKMappingOperationQueue new];
sourceObject = [object retain];
mappingProvider = theMappingProvider;
errors = [NSMutableArray new];
operationQueue = [RKMappingOperationQueue new];
context = RKObjectMappingProviderContextObjectsByKeyPath;
}
return self;
}
- (void)dealloc {
[_sourceObject release];
[_errors release];
[_operationQueue release];
[sourceObject release];
[errors release];
[operationQueue release];
[super dealloc];
}
#pragma mark - Errors
- (NSArray *)errors {
return [NSArray arrayWithArray:errors];
}
- (NSUInteger)errorCount {
return [self.errors count];
}
- (void)addError:(NSError*)error {
- (void)addError:(NSError *)error {
NSAssert(error, @"Cannot add a nil error");
[_errors addObject:error];
[errors addObject:error];
if ([self.delegate respondsToSelector:@selector(objectMapper:didAddError:)]) {
[self.delegate objectMapper:self didAddError:error];
@@ -74,18 +80,18 @@
RKLogWarning(@"Adding mapping error: %@", [error localizedDescription]);
}
- (void)addErrorWithCode:(RKObjectMapperErrorCode)errorCode message:(NSString*)errorMessage keyPath:(NSString*)keyPath userInfo:(NSDictionary*)otherInfo {
NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- (void)addErrorWithCode:(RKObjectMapperErrorCode)errorCode message:(NSString *)errorMessage keyPath:(NSString *)keyPath userInfo:(NSDictionary *)otherInfo {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
errorMessage, NSLocalizedDescriptionKey,
@"RKObjectMapperKeyPath", keyPath ? keyPath : (NSString*) [NSNull null],
@"RKObjectMapperKeyPath", keyPath ? keyPath : (NSString *) [NSNull null],
nil];
[userInfo addEntriesFromDictionary:otherInfo];
NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:errorCode userInfo:userInfo];
NSError *error = [NSError errorWithDomain:RKRestKitErrorDomain code:errorCode userInfo:userInfo];
[self addError:error];
}
- (void)addErrorForUnmappableKeyPath:(NSString*)keyPath {
NSString* errorMessage = [NSString stringWithFormat:@"Could not find an object mapping for keyPath: '%@'", keyPath];
- (void)addErrorForUnmappableKeyPath:(NSString *)keyPath {
NSString *errorMessage = [NSString stringWithFormat:@"Could not find an object mapping for keyPath: '%@'", keyPath];
[self addErrorWithCode:RKObjectMapperErrorObjectMappingNotFound message:errorMessage keyPath:keyPath userInfo:nil];
}
@@ -111,22 +117,22 @@
#pragma mark - Mapping Primitives
- (id)mapObject:(id)mappableObject atKeyPath:(NSString*)keyPath usingMapping:(id<RKObjectMappingDefinition>)mapping {
- (id)mapObject:(id)mappableObject atKeyPath:(NSString *)keyPath usingMapping:(id<RKObjectMappingDefinition>)mapping {
NSAssert([mappableObject respondsToSelector:@selector(setValue:forKeyPath:)], @"Expected self.object to be KVC compliant");
id destinationObject = nil;
if (self.targetObject) {
destinationObject = self.targetObject;
RKObjectMapping* objectMapping = nil;
RKObjectMapping *objectMapping = nil;
if ([mapping isKindOfClass:[RKDynamicObjectMapping class]]) {
objectMapping = [(RKDynamicObjectMapping*)mapping objectMappingForDictionary:mappableObject];
objectMapping = [(RKDynamicObjectMapping *)mapping objectMappingForDictionary:mappableObject];
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping*)mapping;
objectMapping = (RKObjectMapping *)mapping;
} else {
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([mapping class]));
}
if (NO == [[self.targetObject class] isSubclassOfClass:objectMapping.objectClass]) {
NSString* errorMessage = [NSString stringWithFormat:
NSString *errorMessage = [NSString stringWithFormat:
@"Expected an object mapping for class of type '%@', provider returned one for '%@'",
NSStringFromClass([self.targetObject class]), NSStringFromClass(objectMapping.objectClass)];
[self addErrorWithCode:RKObjectMapperErrorObjectMappingTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil];
@@ -150,19 +156,19 @@
return nil;
}
- (NSArray*)mapCollection:(NSArray*)mappableObjects atKeyPath:(NSString*)keyPath usingMapping:(id<RKObjectMappingDefinition>)mapping {
- (NSArray *)mapCollection:(NSArray *)mappableObjects atKeyPath:(NSString *)keyPath usingMapping:(id<RKObjectMappingDefinition>)mapping {
NSAssert(mappableObjects != nil, @"Cannot map without an collection of mappable objects");
NSAssert(mapping != nil, @"Cannot map without a mapping to consult");
NSArray* objectsToMap = mappableObjects;
NSArray *objectsToMap = mappableObjects;
if (mapping.forceCollectionMapping) {
// If we have forced mapping of a dictionary, map each subdictionary
if ([mappableObjects isKindOfClass:[NSDictionary class]]) {
RKLogDebug(@"Collection mapping forced for NSDictionary, mapping each key/value independently...");
objectsToMap = [NSMutableArray arrayWithCapacity:[mappableObjects count]];
for (id key in mappableObjects) {
NSDictionary* dictionaryToMap = [NSDictionary dictionaryWithObject:[mappableObjects valueForKey:key] forKey:key];
[(NSMutableArray*)objectsToMap addObject:dictionaryToMap];
NSDictionary *dictionaryToMap = [NSDictionary dictionaryWithObject:[mappableObjects valueForKey:key] forKey:key];
[(NSMutableArray *)objectsToMap addObject:dictionaryToMap];
}
} else {
RKLogWarning(@"Collection mapping forced but mappable objects is of type '%@' rather than NSDictionary", NSStringFromClass([mappableObjects class]));
@@ -170,9 +176,9 @@
}
// Ensure we are mapping onto a mutable collection if there is a target
NSMutableArray* mappedObjects = self.targetObject ? self.targetObject : [NSMutableArray arrayWithCapacity:[mappableObjects count]];
NSMutableArray *mappedObjects = self.targetObject ? self.targetObject : [NSMutableArray arrayWithCapacity:[mappableObjects count]];
if (NO == [mappedObjects respondsToSelector:@selector(addObject:)]) {
NSString* errorMessage = [NSString stringWithFormat:
NSString *errorMessage = [NSString stringWithFormat:
@"Cannot map a collection of objects onto a non-mutable collection. Unexpected destination object type '%@'",
NSStringFromClass([mappedObjects class])];
[self addErrorWithCode:RKObjectMapperErrorObjectMappingTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil];
@@ -205,12 +211,12 @@
[self.delegate objectMapper:self willMapFromObject:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
}
NSError* error = nil;
NSError *error = nil;
RKObjectMappingOperation* operation = [RKObjectMappingOperation mappingOperationFromObject:mappableObject
RKObjectMappingOperation *operation = [RKObjectMappingOperation mappingOperationFromObject:mappableObject
toObject:destinationObject
withMapping:mapping];
operation.queue = _operationQueue;
operation.queue = operationQueue;
BOOL success = [operation performMapping:&error];
if (success) {
if ([self.delegate respondsToSelector:@selector(objectMapper:didMapFromObject:toObject:atKeyPath:usingMapping:)]) {
@@ -228,14 +234,14 @@
- (id)objectWithMapping:(id<RKObjectMappingDefinition>)mapping andData:(id)mappableData {
NSAssert([mapping conformsToProtocol:@protocol(RKObjectMappingDefinition)], @"Expected an object implementing RKObjectMappingDefinition");
RKObjectMapping* objectMapping = nil;
RKObjectMapping *objectMapping = nil;
if ([mapping isKindOfClass:[RKDynamicObjectMapping class]]) {
objectMapping = [(RKDynamicObjectMapping*)mapping objectMappingForDictionary:mappableData];
objectMapping = [(RKDynamicObjectMapping *)mapping objectMappingForDictionary:mappableData];
if (! objectMapping) {
RKLogDebug(@"Mapping %@ declined mapping for data %@: returned nil objectMapping", mapping, mappableData);
}
} else if ([mapping isKindOfClass:[RKObjectMapping class]]) {
objectMapping = (RKObjectMapping*)mapping;
objectMapping = (RKObjectMapping *)mapping;
} else {
NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([mapping class]));
}
@@ -247,24 +253,25 @@
return nil;
}
// Primary entry point for the mapper.
- (RKObjectMappingResult*)performMapping {
NSAssert(self.sourceObject != nil, @"Cannot perform object mapping without a source object to map from");
NSAssert(self.mappingProvider != nil, @"Cannot perform object mapping without an object mapping provider");
RKLogDebug(@"Performing object mapping sourceObject: %@\n and targetObject: %@", self.sourceObject, self.targetObject);
if ([self.delegate respondsToSelector:@selector(objectMapperWillBeginMapping:)]) {
[self.delegate objectMapperWillBeginMapping:self];
- (id)performMappingForObject:(id)mappableValue atKeyPath:(NSString *)keyPath usingMapping:(id<RKObjectMappingDefinition>)mapping {
id mappingResult;
if (mapping.forceCollectionMapping || [mappableValue isKindOfClass:[NSArray class]] || [mappableValue isKindOfClass:[NSSet class]]) {
RKLogDebug(@"Found mappable collection at keyPath '%@': %@", keyPath, mappableValue);
mappingResult = [self mapCollection:mappableValue atKeyPath:keyPath usingMapping:mapping];
} else {
RKLogDebug(@"Found mappable data at keyPath '%@': %@", keyPath, mappableValue);
mappingResult = [self mapObject:mappableValue atKeyPath:keyPath usingMapping:mapping];
}
// Perform the mapping
return mappingResult;
}
- (NSMutableDictionary *)performKeyPathMappingUsingMappingDictionary:(NSDictionary *)mappingsByKeyPath {
BOOL foundMappable = NO;
NSMutableDictionary* results = [NSMutableDictionary dictionary];
NSDictionary* mappingsByKeyPath = [self.mappingProvider mappingsByKeyPath];
for (NSString* keyPath in mappingsByKeyPath) {
id mappingResult;
id mappableValue;
NSMutableDictionary *results = [NSMutableDictionary dictionary];
for (NSString *keyPath in mappingsByKeyPath) {
id mappingResult = nil;
id mappableValue = nil;
RKLogTrace(@"Examining keyPath '%@' for mappable content...", keyPath);
@@ -291,22 +298,51 @@
if ([self.delegate respondsToSelector:@selector(objectMapper:didFindMappableObject:atKeyPath:withMapping:)]) {
[self.delegate objectMapper:self didFindMappableObject:mappableValue atKeyPath:keyPath withMapping:mapping];
}
if (mapping.forceCollectionMapping || [mappableValue isKindOfClass:[NSArray class]] || [mappableValue isKindOfClass:[NSSet class]]) {
RKLogDebug(@"Found mappable collection at keyPath '%@': %@", keyPath, mappableValue);
mappingResult = [self mapCollection:mappableValue atKeyPath:keyPath usingMapping:mapping];
} else {
RKLogDebug(@"Found mappable data at keyPath '%@': %@", keyPath, mappableValue);
mappingResult = [self mapObject:mappableValue atKeyPath:keyPath usingMapping:mapping];
}
mappingResult = [self performMappingForObject:mappableValue atKeyPath:keyPath usingMapping:mapping];
if (mappingResult) {
[results setObject:mappingResult forKey:keyPath];
}
}
if (NO == foundMappable) return nil;
return results;
}
// Primary entry point for the mapper.
- (RKObjectMappingResult *)performMapping {
NSAssert(self.sourceObject != nil, @"Cannot perform object mapping without a source object to map from");
NSAssert(self.mappingProvider != nil, @"Cannot perform object mapping without an object mapping provider");
RKLogDebug(@"Performing object mapping sourceObject: %@\n and targetObject: %@", self.sourceObject, self.targetObject);
if ([self.delegate respondsToSelector:@selector(objectMapperWillBeginMapping:)]) {
[self.delegate objectMapperWillBeginMapping:self];
}
// Perform the mapping
BOOL foundMappable = NO;
NSMutableDictionary *results = nil;
// Handle mapping selection for context
id mappingsForContext = [self.mappingProvider valueForContext:context];
if ([mappingsForContext isKindOfClass:[NSDictionary class]]) {
results = [self performKeyPathMappingUsingMappingDictionary:mappingsForContext];
foundMappable = (results != nil);
} else if ([mappingsForContext conformsToProtocol:@protocol(RKObjectMappingDefinition)]) {
id mappableData = self.sourceObject;
if ([mappingsForContext respondsToSelector:@selector(rootKeyPath)] && [mappingsForContext rootKeyPath] != nil) {
mappableData = [self.sourceObject valueForKeyPath:[mappingsForContext rootKeyPath]];
}
id mappingResult = [self performMappingForObject:mappableData atKeyPath:@"" usingMapping:mappingsForContext];
foundMappable = YES;
results = [NSDictionary dictionaryWithObject:mappingResult forKey:@""];
}
// Allow any queued operations to complete
RKLogDebug(@"The following operations are in the queue: %@", _operationQueue.operations);
[_operationQueue waitUntilAllOperationsAreFinished];
RKLogDebug(@"The following operations are in the queue: %@", operationQueue.operations);
[operationQueue waitUntilAllOperationsAreFinished];
if ([self.delegate respondsToSelector:@selector(objectMapperDidFinishMapping:)]) {
[self.delegate objectMapperDidFinishMapping:self];

View File

@@ -121,7 +121,7 @@ NSString* const RKObjectMappingNestingAttributeKeyName = @"<RK_NESTING_ATTRIBUTE
}
- (NSString*)description {
return [NSString stringWithFormat:@"RKObjectMapping class => %@: keyPath mappings => %@", NSStringFromClass(self.objectClass), _mappings];
return [NSString stringWithFormat:@"<%@:%p objectClass=%@ keyPath mappings => %@>", NSStringFromClass([self class]), self, NSStringFromClass(self.objectClass), _mappings];
}
- (id)mappingForKeyPath:(NSString*)keyPath {

View File

@@ -21,6 +21,16 @@
#import "RKObjectMapping.h"
#import "RKDynamicObjectMapping.h"
// Internal framework contexts
typedef enum {
RKObjectMappingProviderContextObjectsByKeyPath = 1000,
RKObjectMappingProviderContextObjectsByType,
RKObjectMappingProviderContextObjectsByURL,
RKObjectMappingProviderContextSerialization,
RKObjectMappingProviderContextErrors,
RKObjectMappingProviderContextPagination
} RKObjectMappingProviderContext;
/**
The mapping provider is a repository of registered object mappings for use by instances
of RKObjectManager and RKObjectMapper. It provides for the storage and retrieval of object
@@ -42,14 +52,11 @@
that they target.
*/
@interface RKObjectMappingProvider : NSObject {
NSMutableArray *_objectMappings;
NSMutableDictionary *_mappingsByKeyPath;
NSMutableDictionary *_serializationMappings;
NSMutableArray *_errorMappings;
NSMutableDictionary *mappingContexts;
}
/**
Returns a new autoreleased object mapping provider
Creates and returns an autoreleased RKObjectMappingProvider instance.
@return A new autoreleased object mapping provider instance.
*/
@@ -197,16 +204,13 @@
- (RKObjectMapping *)serializationMappingForClass:(Class)objectClass;
/**
Adds an object mapping to the provider to be used when mapping a payload known
to contain an error representation.
An object mapping used when the remote system returns an error status code
and a payload with a MIME Type that RestKit is capable of parsing.
@param errorMapping The error mapping to add to the provider
@see RKObjectLoader
@see RKParserRegistry
*/
// TODO: Should these be key-path based?
- (void)addErrorMapping:(RKObjectMapping *)errorMapping;
- (void)removeErrorMapping:(RKObjectMapping *)errorMapping;
- (NSArray *)errorMappings;
@property (nonatomic, retain) RKObjectMapping *errorMapping;
/**
An object mapping used when mapping pagination metadata (current page, object count, etc)
@@ -215,12 +219,12 @@
For example, if using the popular will_paginate plugin with Ruby on Rails, we would configure
our pagination mapping like so:
// Assumes the JSON format of http://stackoverflow.com/questions/4699182/will-paginate-json-support
RKObjectMapping *paginationMapping = [RKObjectMapping mappingForClass:[RKObjectPaginator class]];
[paginationMapping mapKeyPath:@"current_page" toAttribute:@"currentPage"];
[paginationMapping mapKeyPath:@"per_page" toAttribute:@"perPage"];
[paginationMapping mapKeyPath:@"total_entries" toAttribute:@"objectCount"];
// Assumes the JSON format of http://stackoverflow.com/questions/4699182/will-paginate-json-support
RKObjectMapping *paginationMapping = [RKObjectMapping mappingForClass:[RKObjectPaginator class]];
[paginationMapping mapKeyPath:@"current_page" toAttribute:@"currentPage"];
[paginationMapping mapKeyPath:@"per_page" toAttribute:@"perPage"];
[paginationMapping mapKeyPath:@"total_entries" toAttribute:@"objectCount"];
@see RKObjectPaginator
*/
@@ -228,6 +232,27 @@
@end
@interface RKObjectMappingProvider (Contexts)
- (void)initializeContext:(RKObjectMappingProviderContext)context withValue:(id)value;
- (id)valueForContext:(RKObjectMappingProviderContext)context;
- (void)setValue:(id)value forContext:(RKObjectMappingProviderContext)context;
- (id<RKObjectMappingDefinition>)mappingForContext:(RKObjectMappingProviderContext)context;
/**
Stores a single object mapping for a given context. Useful when a component needs to enable
configuration via one (and only one) object mapping.
*/
- (void)setMapping:(id<RKObjectMappingDefinition>)mapping context:(RKObjectMappingProviderContext)context;
- (NSArray *)mappingsForContext:(RKObjectMappingProviderContext)context;
- (void)addMapping:(id<RKObjectMappingDefinition>)mapping context:(RKObjectMappingProviderContext)context;
- (void)removeMapping:(id<RKObjectMappingDefinition>)mapping context:(RKObjectMappingProviderContext)context;
- (id<RKObjectMappingDefinition>)mappingForKeyPath:(NSString *)keyPath context:(RKObjectMappingProviderContext)context;
- (void)setMapping:(id<RKObjectMappingDefinition>)mapping forKeyPath:(NSString *)keyPath context:(RKObjectMappingProviderContext)context;
- (void)removeMappingForKeyPath:(NSString *)keyPath context:(RKObjectMappingProviderContext)context;
@end
// Method signatures being phased out
@interface RKObjectMappingProvider (CompatibilityAliases)
+ (RKObjectMappingProvider *)objectMappingProvider;

View File

@@ -22,8 +22,6 @@
@implementation RKObjectMappingProvider
@synthesize paginationMapping;
+ (RKObjectMappingProvider *)mappingProvider {
return [[self new] autorelease];
}
@@ -31,42 +29,43 @@
- (id)init {
self = [super init];
if (self) {
_objectMappings = [NSMutableArray new];
_mappingsByKeyPath = [NSMutableDictionary new];
_serializationMappings = [NSMutableDictionary new];
mappingContexts = [NSMutableDictionary new];
[self initializeContext:RKObjectMappingProviderContextObjectsByKeyPath withValue:[NSMutableDictionary dictionary]];
[self initializeContext:RKObjectMappingProviderContextObjectsByType withValue:[NSMutableArray array]];
[self initializeContext:RKObjectMappingProviderContextObjectsByURL withValue:[NSMutableDictionary dictionary]];
[self initializeContext:RKObjectMappingProviderContextSerialization withValue:[NSMutableDictionary dictionary]];
[self initializeContext:RKObjectMappingProviderContextErrors withValue:[NSMutableArray array]];
}
return self;
}
- (void)dealloc {
[_objectMappings release];
[_mappingsByKeyPath release];
[_serializationMappings release];
[mappingContexts release];
[super dealloc];
}
- (void)setObjectMapping:(id<RKObjectMappingDefinition>)objectOrDynamicMapping forKeyPath:(NSString *)keyPath {
[_mappingsByKeyPath setValue:objectOrDynamicMapping forKey:keyPath];
[self setMapping:objectOrDynamicMapping forKeyPath:keyPath context:RKObjectMappingProviderContextObjectsByKeyPath];
}
- (void)removeObjectMappingForKeyPath:(NSString *)keyPath {
[_mappingsByKeyPath removeObjectForKey:keyPath];
[self removeMappingForKeyPath:keyPath context:RKObjectMappingProviderContextObjectsByKeyPath];
}
- (id<RKObjectMappingDefinition>)objectMappingForKeyPath:(NSString *)keyPath {
return [_mappingsByKeyPath objectForKey:keyPath];
return [self mappingForKeyPath:keyPath context:RKObjectMappingProviderContextObjectsByKeyPath];
}
- (void)setSerializationMapping:(RKObjectMapping *)mapping forClass:(Class)objectClass {
[_serializationMappings setValue:mapping forKey:NSStringFromClass(objectClass)];
[self setMapping:mapping forKeyPath:NSStringFromClass(objectClass) context:RKObjectMappingProviderContextSerialization];
}
- (RKObjectMapping*)serializationMappingForClass:(Class)objectClass {
return (RKObjectMapping *)[_serializationMappings objectForKey:NSStringFromClass(objectClass)];
- (RKObjectMapping *)serializationMappingForClass:(Class)objectClass {
return [self mappingForKeyPath:NSStringFromClass(objectClass) context:RKObjectMappingProviderContextSerialization];
}
- (NSDictionary*)objectMappingsByKeyPath {
return _mappingsByKeyPath;
return [NSDictionary dictionaryWithDictionary:(NSDictionary *) [self valueForContext:RKObjectMappingProviderContextObjectsByKeyPath]];
}
- (void)registerObjectMapping:(RKObjectMapping *)objectMapping withRootKeyPath:(NSString *)keyPath {
@@ -79,12 +78,14 @@
}
- (void)addObjectMapping:(RKObjectMapping *)objectMapping {
[_objectMappings addObject:objectMapping];
[self addMapping:objectMapping context:RKObjectMappingProviderContextObjectsByType];
}
- (NSArray *)objectMappingsForClass:(Class)theClass {
NSMutableArray *mappings = [NSMutableArray array];
NSArray *mappingsToSearch = [[NSArray arrayWithArray:_objectMappings] arrayByAddingObjectsFromArray:[_mappingsByKeyPath allValues]];
NSArray *mappingByType = [self valueForContext:RKObjectMappingProviderContextObjectsByType];
NSArray *mappingByKeyPath = [[self valueForContext:RKObjectMappingProviderContextObjectsByKeyPath] allValues];
NSArray *mappingsToSearch = [[NSArray arrayWithArray:mappingByType] arrayByAddingObjectsFromArray:mappingByKeyPath];
for (NSObject <RKObjectMappingDefinition> *candidateMapping in mappingsToSearch) {
if ( ![candidateMapping respondsToSelector:@selector(objectClass)] || [mappings containsObject:candidateMapping])
continue;
@@ -101,16 +102,108 @@
return ([objectMappings count] > 0) ? [objectMappings objectAtIndex:0] : nil;
}
- (void)addErrorMapping:(RKObjectMapping *)errorMapping {
[_errorMappings addObject:errorMapping];
#pragma mark - Error Mappings
- (RKObjectMapping *)errorMapping {
return [self mappingForContext:RKObjectMappingProviderContextErrors];
}
- (void)removeErrorMapping:(RKObjectMapping *)errorMapping {
[_errorMappings removeObject:errorMapping];
- (void)setErrorMapping:(RKObjectMapping *)errorMapping {
[self setMapping:errorMapping context:RKObjectMappingProviderContextErrors];
}
- (NSArray *)errorMappings {
return [[_errorMappings copy] autorelease];
#pragma mark - Pagination Mapping
- (RKObjectMapping *)paginationMapping {
return [self mappingForContext:RKObjectMappingProviderContextPagination];
}
- (void)setPaginationMapping:(RKObjectMapping *)paginationMapping {
[self setMapping:paginationMapping context:RKObjectMappingProviderContextPagination];
}
#pragma mark - Mapping Context Primitives
- (void)initializeContext:(RKObjectMappingProviderContext)context withValue:(id)value {
NSAssert([self valueForContext:context] == nil, @"Attempt to reinitialized an existing mapping provider context.");
[self setValue:value forContext:context];
}
- (id)valueForContext:(RKObjectMappingProviderContext)context {
NSNumber *contextNumber = [NSNumber numberWithInteger:context];
return [mappingContexts objectForKey:contextNumber];
}
- (void)setValue:(id)value forContext:(RKObjectMappingProviderContext)context {
NSNumber *contextNumber = [NSNumber numberWithInteger:context];
[mappingContexts setObject:value forKey:contextNumber];
}
- (void)assertStorageForContext:(RKObjectMappingProviderContext)context isKindOfClass:(Class)theClass {
id contextValue = [self valueForContext:context];
NSAssert([contextValue isKindOfClass:theClass], @"Storage type mismatch for context %d: expected a %@, got %@.", context, theClass, [contextValue class]);
}
- (void)setMapping:(id<RKObjectMappingDefinition>)mapping context:(RKObjectMappingProviderContext)context {
NSNumber *contextNumber = [NSNumber numberWithInteger:context];
[mappingContexts setObject:mapping forKey:contextNumber];
}
- (id<RKObjectMappingDefinition>)mappingForContext:(RKObjectMappingProviderContext)context {
id contextValue = [self valueForContext:context];
if (contextValue == nil) return nil;
Protocol *protocol = @protocol(RKObjectMappingDefinition);
NSAssert([contextValue conformsToProtocol:protocol], @"Storage type mismatch for context %d: expected a %@, got %@.", context, protocol, [contextValue class]);
return contextValue;
}
- (NSArray *)mappingsForContext:(RKObjectMappingProviderContext)context {
id contextValue = [self valueForContext:context];
if (contextValue == nil) return [NSArray array];
[self assertStorageForContext:context isKindOfClass:[NSArray class]];
return [NSArray arrayWithArray:contextValue];
}
- (void)addMapping:(id<RKObjectMappingDefinition>)mapping context:(RKObjectMappingProviderContext)context {
NSMutableArray *contextValue = [self valueForContext:context];
if (contextValue == nil) {
contextValue = [NSMutableArray arrayWithCapacity:1];
[self setValue:contextValue forContext:context];
}
[self assertStorageForContext:context isKindOfClass:[NSArray class]];
[contextValue addObject:mapping];
}
- (void)removeMapping:(id<RKObjectMappingDefinition>)mapping context:(RKObjectMappingProviderContext)context {
NSMutableArray *contextValue = [self valueForContext:context];
NSAssert(contextValue, @"Attempted to remove mapping from undefined context: %d", context);
[self assertStorageForContext:context isKindOfClass:[NSArray class]];
NSAssert([contextValue containsObject:mapping], @"Attempted to remove mapping from collection that does not include it for context: %d", context);
[contextValue removeObject:mapping];
}
- (id<RKObjectMappingDefinition>)mappingForKeyPath:(NSString *)keyPath context:(RKObjectMappingProviderContext)context {
NSMutableDictionary *contextValue = [self valueForContext:context];
NSAssert(contextValue, @"Attempted to retrieve mapping from undefined context: %d", context);
[self assertStorageForContext:context isKindOfClass:[NSDictionary class]];
return [contextValue valueForKey:keyPath];
}
- (void)setMapping:(id<RKObjectMappingDefinition>)mapping forKeyPath:(NSString *)keyPath context:(RKObjectMappingProviderContext)context {
NSMutableDictionary *contextValue = [self valueForContext:context];
if (contextValue == nil) {
contextValue = [NSMutableDictionary dictionary];
[self setValue:contextValue forContext:context];
}
[self assertStorageForContext:context isKindOfClass:[NSDictionary class]];
[contextValue setValue:mapping forKey:keyPath];
}
- (void)removeMappingForKeyPath:(NSString *)keyPath context:(RKObjectMappingProviderContext)context {
NSMutableDictionary *contextValue = [self valueForContext:context];
[self assertStorageForContext:context isKindOfClass:[NSDictionary class]];
[contextValue removeObjectForKey:keyPath];
}
#pragma mark - Aliases

View File

@@ -121,21 +121,63 @@
/// The number of objects to load per page
@property (nonatomic, assign) NSUInteger perPage;
/// The number of pages in the total collection
@property (nonatomic, readonly) NSUInteger pageCount;
/// The total number of objects in the collection
@property (nonatomic, readonly) NSUInteger objectCount;
/// The current page number the paginator has loaded
@property (nonatomic, readonly) NSUInteger currentPage;
/// Returns YES when the paginator has loaded a page of objects
@property (nonatomic, readonly, getter = isLoaded) BOOL loaded;
/// Returns YES when there is a next page to load
/**
Returns the page number for the most recently loaded page of objects.
@return The page number for the current page of objects.
@exception NSInternalInconsistencyException Raised if isLoaded is NO.
*/
@property (nonatomic, readonly) NSUInteger currentPage;
/**
Returns the number of pages in the total resource collection.
@return A count of the number of pages in the resource collection.
@exception NSInternalInconsistencyException Raised if hasPageCount is NO.
*/
@property (nonatomic, readonly) NSUInteger pageCount;
/**
Returns the total number of objects in the collection
@return A count of the number of objects in the resource collection.
@exception NSInternalInconsistencyException Raised if hasObjectCount is NO.
*/
@property (nonatomic, readonly) NSUInteger objectCount;
/**
Returns a Boolean value indicating if the total number of pages in the collection
is known by the paginator.
@return YES if the paginator knows the page count, otherwise NO
*/
- (BOOL)hasPageCount;
/**
Returns a Boolean value indicating if the total number of objects in the collection
is known by the paginator.
@return YES if the paginator knows the number of objects in the paginated collection, otherwise NO.
*/
- (BOOL)hasObjectCount;
/**
Returns a Boolean value indicating if there is a next page in the collection.
@return YES if there is a next page, otherwise NO.
@exception NSInternalInconsistencyException Raised if isLoaded or hasPageCount is NO.
*/
- (BOOL)hasNextPage;
/// Returns YES when there is a previous page to load
/**
Returns a Boolean value indicating if there is a previous page in the collection.
@return YES if there is a previous page, otherwise NO.
@exception NSInternalInconsistencyException Raised if isLoaded is NO.
*/
- (BOOL)hasPreviousPage;
/** @name Paginator Actions */
@@ -199,13 +241,6 @@
*/
- (void)paginator:(RKObjectPaginator *)paginator willLoadPage:(NSUInteger)page objectLoader:(RKObjectLoader *)loader;
/**
Sent to the delegate when the paginator has loaded the last page in the collection
@param paginator The paginator instance that has loaded the last page
*/
- (void)paginatorDidLoadLastPage:(RKObjectPaginator *)paginator;
/**
Sent to the delegate when the paginator has loaded the first page in the collection
@@ -213,4 +248,11 @@
*/
- (void)paginatorDidLoadFirstPage:(RKObjectPaginator *)paginator;
/**
Sent to the delegate when the paginator has loaded the last page in the collection
@param paginator The paginator instance that has loaded the last page
*/
- (void)paginatorDidLoadLastPage:(RKObjectPaginator *)paginator;
@end

View File

@@ -54,7 +54,9 @@ static NSUInteger RKObjectPaginatorDefaultPerPage = 25;
if (self) {
patternURL = [aPatternURL copy];
mappingProvider = [aMappingProvider retain];
currentPage = 0;
currentPage = NSUIntegerMax;
pageCount = NSUIntegerMax;
objectCount = NSUIntegerMax;
perPage = RKObjectPaginatorDefaultPerPage;
loaded = NO;
}
@@ -72,6 +74,8 @@ static NSUInteger RKObjectPaginatorDefaultPerPage = 25;
mappingProvider = nil;
[objectStore release];
objectStore = nil;
[objectLoader cancel];
objectLoader.delegate = nil;
[objectLoader release];
objectLoader = nil;
@@ -86,6 +90,42 @@ static NSUInteger RKObjectPaginatorDefaultPerPage = 25;
return [patternURL URLByInterpolatingResourcePathWithObject:self];
}
// Private. Public consumers can rely on isLoaded
- (BOOL)hasCurrentPage {
return currentPage != NSUIntegerMax;
}
- (BOOL)hasPageCount {
return pageCount != NSUIntegerMax;
}
- (BOOL)hasObjectCount {
return objectCount != NSUIntegerMax;
}
- (NSUInteger)currentPage {
// Referenced during initial load, so we don't rely on isLoaded.
NSAssert([self hasCurrentPage], @"Current page has not been initialized.");
return currentPage;
}
- (NSUInteger)pageCount {
NSAssert([self hasPageCount], @"Page count not available.");
return pageCount;
}
- (BOOL)hasNextPage {
NSAssert(self.isLoaded, @"Cannot determine hasNextPage: paginator is not loaded.");
NSAssert([self hasPageCount], @"Cannot determine hasNextPage: page count is not known.");
return self.currentPage < self.pageCount;
}
- (BOOL)hasPreviousPage {
NSAssert(self.isLoaded, @"Cannot determine hasPreviousPage: paginator is not loaded.");
return self.currentPage > 1;
}
#pragma mark - RKObjectLoaderDelegate methods
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
@@ -93,6 +133,18 @@ static NSUInteger RKObjectPaginatorDefaultPerPage = 25;
loaded = YES;
RKLogInfo(@"Loaded objects: %@", objects);
[self.delegate paginator:self didLoadObjects:objects forPage:self.currentPage];
if ([self hasPageCount] && self.currentPage == 1) {
if ([self.delegate respondsToSelector:@selector(paginatorDidLoadFirstPage:)]) {
[self.delegate paginatorDidLoadFirstPage:self];
}
}
if ([self hasPageCount] && self.currentPage == self.pageCount) {
if ([self.delegate respondsToSelector:@selector(paginatorDidLoadLastPage:)]) {
[self.delegate paginatorDidLoadLastPage:self];
}
}
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
@@ -107,28 +159,17 @@ static NSUInteger RKObjectPaginatorDefaultPerPage = 25;
BOOL success = [mappingOperation performMapping:&error];
if (!success) {
pageCount = currentPage = 0;
RKLogError(@"Paginator didn't map info to compute page count. Assuming no pages.");
} else if (self.perPage) {
RKLogError(@"Paginator didn't map info to compute page count. Assuming no pages.");
} else if (self.perPage && [self hasObjectCount]) {
float objectCountFloat = self.objectCount;
pageCount = ceilf( objectCountFloat / self.perPage);
pageCount = ceilf(objectCountFloat / self.perPage);
RKLogInfo(@"Paginator objectCount: %d pageCount: %d", self.objectCount, self.pageCount);
} else {
} else {
NSAssert(NO, @"Paginator perPage set is 0.");
RKLogError(@"Paginator perPage set is 0.");
}
}
- (BOOL)hasNextPage {
NSAssert(self.isLoaded, @"Cannot determine hasNextPage: paginator is not loaded.");
return self.currentPage < self.pageCount;
}
- (BOOL)hasPreviousPage {
NSAssert(self.isLoaded, @"Cannot determine hasPreviousPage: paginator is not loaded.");
return self.currentPage > 0;
}
#pragma mark - Action methods
- (void)loadNextPage {
@@ -151,6 +192,11 @@ static NSUInteger RKObjectPaginatorDefaultPerPage = 25;
}
self.objectLoader.method = RKRequestMethodGET;
self.objectLoader.delegate = self;
if ([self.delegate respondsToSelector:@selector(paginator:willLoadPage:objectLoader:)]) {
[self.delegate paginator:self willLoadPage:pageNumber objectLoader:self.objectLoader];
}
[self.objectLoader send];
}

View File

@@ -10,8 +10,8 @@
250CA67D147D8E8B0047D347 /* OCHamcrest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 250CA67B147D8E800047D347 /* OCHamcrest.framework */; };
250CA67E147D8E8F0047D347 /* OCHamcrestIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 250CA67C147D8E800047D347 /* OCHamcrestIOS.framework */; };
250CA680147D8F050047D347 /* OCHamcrest.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 250CA67B147D8E800047D347 /* OCHamcrest.framework */; };
2513504E14B8FE6B00A7E893 /* RKConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2513504D14B8FE6B00A7E893 /* RKConfigurationDelegate.h */; };
2513504F14B8FE6B00A7E893 /* RKConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2513504D14B8FE6B00A7E893 /* RKConfigurationDelegate.h */; };
2513504E14B8FE6B00A7E893 /* RKConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2513504D14B8FE6B00A7E893 /* RKConfigurationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
2513504F14B8FE6B00A7E893 /* RKConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2513504D14B8FE6B00A7E893 /* RKConfigurationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
25160D1A14564E810060A5C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D1914564E810060A5C5 /* Foundation.framework */; };
25160D2814564E820060A5C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D2714564E820060A5C5 /* SenTestingKit.framework */; };
25160D2A14564E820060A5C5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25160D2914564E820060A5C5 /* UIKit.framework */; };

View File

@@ -113,8 +113,8 @@
RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease];
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"" toKeyPath:@"errorMessage"]];
[provider setMapping:errorMapping forKeyPath:@"error"];
[provider setMapping:errorMapping forKeyPath:@"errors"];
errorMapping.rootKeyPath = @"errors";
provider.errorMapping = errorMapping;
return provider;
}

View File

@@ -64,7 +64,7 @@
}
- (void)testShouldAllowYouToRemoveAMappingByKeyPath {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider objectMappingProvider];
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKManagedObjectMapping* catMapping = [RKManagedObjectMapping mappingForClass:[RKCat class]];
assertThat(catMapping, isNot(equalTo(nil)));
[catMapping mapAttributes:@"name", nil];
@@ -76,4 +76,94 @@
assertThat(returnedMapping, is(nilValue()));
}
- (void)testSettingMappingInAContext {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
STAssertNoThrow([mappingProvider setMapping:mapping context:1000], nil);
}
- (void)testRetrievalOfMapping {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider setMapping:mapping context:1000];
assertThat([mappingProvider mappingForContext:1000], is(equalTo(mapping)));
}
- (void)testRetrievalOfMappingsCollectionForUndefinedContextReturnsEmptyArray {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
NSArray *collection = [mappingProvider mappingsForContext:1000];
assertThat(collection, is(empty()));
}
- (void)testRetrievalOfMappingsCollectionWhenSingleMappingIsStoredRaisesError {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider setMapping:mapping context:1000];
STAssertThrows([mappingProvider mappingsForContext:1000], @"Expected collection mapping retrieval to throw due to storage of single mapping");
}
- (void)testAddingMappingToCollectionContext {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
STAssertNoThrow([mappingProvider addMapping:mapping context:1000], nil);
}
- (void)testRetrievalOfMappingCollection {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping_1 = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider addMapping:mapping_1 context:1000];
RKObjectMapping *mapping_2 = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider addMapping:mapping_2 context:1000];
NSArray *collection = [mappingProvider mappingsForContext:1000];
assertThat(collection, hasItems(mapping_1, mapping_2, nil));
}
- (void)testRetrievalOfMappingCollectionReturnsImmutableArray {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping_1 = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider addMapping:mapping_1 context:1000];
RKObjectMapping *mapping_2 = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider addMapping:mapping_2 context:1000];
NSArray *collection = [mappingProvider mappingsForContext:1000];
assertThat(collection, isNot(instanceOf([NSMutableArray class])));
}
- (void)testRemovalOfMappingFromCollection {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping_1 = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider addMapping:mapping_1 context:1000];
RKObjectMapping *mapping_2 = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider addMapping:mapping_2 context:1000];
[mappingProvider removeMapping:mapping_1 context:1000];
NSArray *collection = [mappingProvider mappingsForContext:1000];
assertThat(collection, onlyContains(mapping_2, nil));
}
- (void)testAttemptToRemoveMappingFromContextThatDoesNotIncludeItRaisesError {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
STAssertThrows([mappingProvider removeMapping:mapping context:1000], @"Removal of mapping not included in context should raise an error.");
}
- (void)testSettingMappingForKeyPathInContext {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
STAssertNoThrow([mappingProvider setMapping:mapping forKeyPath:@"testing" context:1000], nil);
}
- (void)testRetrievalOfMappingForKeyPathInContext {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider setMapping:mapping forKeyPath:@"testing" context:1000];
assertThat([mappingProvider mappingForKeyPath:@"testing" context:1000], is(equalTo(mapping)));
}
- (void)testRemovalOfMappingByKeyPathInContext {
RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider mappingProvider];
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableArray class]];
[mappingProvider setMapping:mapping forKeyPath:@"testing" context:1000];
[mappingProvider removeMappingForKeyPath:@"testing" context:1000];
assertThat([mappingProvider mappingForKeyPath:@"testing" context:1000], is(nilValue()));
}
@end

View File

@@ -111,6 +111,18 @@ NSString * const RKSpecPaginatorDelegateTimeoutException = @"RKSpecPaginatorDele
self.paginationError = error;
}
- (void)paginator:(RKObjectPaginator *)paginator willLoadPage:(NSUInteger)page objectLoader:(RKObjectLoader *)loader {
// Necessary for OCMock expectations
}
- (void)paginatorDidLoadFirstPage:(RKObjectPaginator *)paginator {
// Necessary for OCMock expectations
}
- (void)paginatorDidLoadLastPage:(RKObjectPaginator *)paginator {
// Necessary for OCMock expectations
}
@end
@interface RKObjectPaginatorSpec : RKSpec {
@@ -150,6 +162,18 @@ static NSString * const RKObjectPaginatorSpecResourcePathPattern = @"/paginate?p
assertThat(paginator.mappingProvider, is(equalTo(mappingProvider)));
}
- (void)testInitDoesNotHavePageCount {
RKURL *patternURL = [RKURL URLWithBaseURLString:@"http://restkit.org" resourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:nil];
assertThatBool([paginator hasPageCount], is(equalToBool(NO)));
}
- (void)testInitDoesNotHaveObjectCount {
RKURL *patternURL = [RKURL URLWithBaseURLString:@"http://restkit.org" resourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:nil];
assertThatBool([paginator hasObjectCount], is(equalToBool(NO)));
}
- (void)testThatLoadWithNilMappingProviderRaisesException {
RKURL *patternURL = [RKURL URLWithBaseURLString:@"http://restkit.org" resourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:nil];
@@ -168,13 +192,19 @@ static NSString * const RKObjectPaginatorSpecResourcePathPattern = @"/paginate?p
- (void)testThatResourcePathPatternEvaluatesAgainstPaginator {
RKURL *patternURL = [RKURL URLWithBaseURLString:@"http://restkit.org" resourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:nil];
assertThat([[paginator URL] resourcePath], is(equalTo(@"/paginate?per_page=25&page=0")));
id mockPaginator = [OCMockObject partialMockForObject:paginator];
NSUInteger currentPage = 1;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThat([[paginator URL] resourcePath], is(equalTo(@"/paginate?per_page=25&page=1")));
}
- (void)testThatURLReturnsReflectsStateOfPaginator {
RKURL *patternURL = [RKURL URLWithBaseURLString:@"http://restkit.org" resourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:nil];
assertThat([[paginator URL] absoluteString], is(equalTo(@"http://restkit.org/paginate?page=0&per_page=25")));
id mockPaginator = [OCMockObject partialMockForObject:paginator];
NSUInteger currentPage = 1;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThat([[mockPaginator URL] absoluteString], is(equalTo(@"http://restkit.org/paginate?page=1&per_page=25")));
}
- (void)testLoadingAPageOfObjects {
@@ -196,7 +226,7 @@ static NSString * const RKObjectPaginatorSpecResourcePathPattern = @"/paginate?p
paginator.delegate = testDelegate;
[paginator loadPage:1];
[testDelegate waitForLoad];
assertThatInteger(paginator.perPage, is(equalToInteger(2)));
assertThatInteger(paginator.perPage, is(equalToInteger(3)));
}
- (void)testLoadingPageOfObjectMapsTotalEntries {
@@ -233,6 +263,41 @@ static NSString * const RKObjectPaginatorSpecResourcePathPattern = @"/paginate?p
assertThat([[testDelegate paginatedObjects] valueForKey:@"name"], is(equalTo([NSArray arrayWithObjects:@"Blake", @"Sarah", @"Colin", nil])));
}
- (void)testLoadingPageOfObjectHasPageCount {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:mappingProvider];
RKSpecPaginatorDelegate *testDelegate = [RKSpecPaginatorDelegate paginatorDelegate];
paginator.delegate = testDelegate;
[paginator loadPage:1];
[testDelegate waitForLoad];
assertThatBool([paginator hasPageCount], is(equalToBool(YES)));
}
- (void)testLoadingPageOfObjectHasObjectCount {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:mappingProvider];
RKSpecPaginatorDelegate *testDelegate = [RKSpecPaginatorDelegate paginatorDelegate];
paginator.delegate = testDelegate;
[paginator loadPage:1];
[testDelegate waitForLoad];
assertThatBool([paginator hasObjectCount], is(equalToBool(YES)));
}
- (void)testDelegateIsInformedOfWillLoadPage {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:mappingProvider];
RKSpecPaginatorDelegate *testDelegate = [RKSpecPaginatorDelegate paginatorDelegate];
id mockDelegate = [OCMockObject partialMockForObject:testDelegate];
[[mockDelegate expect] paginator:paginator willLoadPage:1 objectLoader:OCMOCK_ANY];
paginator.delegate = mockDelegate;
[paginator loadPage:1];
[mockDelegate waitForLoad];
[mockDelegate verify];
}
- (void)testDelegateIsInformedOnError {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
@@ -246,6 +311,32 @@ static NSString * const RKObjectPaginatorSpecResourcePathPattern = @"/paginate?p
[mockDelegate verify];
}
- (void)testDelegateIsInformedOnLoadOfFirstPage {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:mappingProvider];
RKSpecPaginatorDelegate *testDelegate = [RKSpecPaginatorDelegate paginatorDelegate];
id mockDelegate = [OCMockObject partialMockForObject:testDelegate];
[[mockDelegate expect] paginatorDidLoadFirstPage:paginator];
paginator.delegate = mockDelegate;
[paginator loadPage:1];
[mockDelegate waitForLoad];
[mockDelegate verify];
}
- (void)testDelegateIsInformedOnLoadOfLastPage {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
RKObjectPaginator *paginator = [RKObjectPaginator paginatorWithPatternURL:patternURL mappingProvider:mappingProvider];
RKSpecPaginatorDelegate *testDelegate = [RKSpecPaginatorDelegate paginatorDelegate];
id mockDelegate = [OCMockObject partialMockForObject:testDelegate];
[[mockDelegate expect] paginatorDidLoadLastPage:paginator];
paginator.delegate = mockDelegate;
[paginator loadPage:2];
[mockDelegate waitForLoad];
[mockDelegate verify];
}
- (void)testLoadingNextPageOfObjects {
RKURL *patternURL = [RKSpecGetBaseURL() URLByAppendingResourcePath:RKObjectPaginatorSpecResourcePathPattern];
RKObjectMappingProvider *mappingProvider = [self paginationMappingProvider];
@@ -287,37 +378,71 @@ static NSString * const RKObjectPaginatorSpecResourcePathPattern = @"/paginate?p
assertThat(testDelegate.paginationError, is(notNilValue()));
}
- (void)itShouldKnowIfItHasANextPage {
- (void)testKnowledgeOfHasANextPage {
RKObjectPaginator *paginator = [[RKObjectPaginator new] autorelease];
id mockPaginator = [OCMockObject partialMockForObject:paginator];
NSUInteger perPage = 5;
NSUInteger currentPage = 1;
NSUInteger objectCount = 10;
BOOL isLoaded = YES;
NSUInteger perPage = 5;
NSUInteger pageCount = 3;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(isLoaded)] isLoaded];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(perPage)] perPage];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(objectCount)] objectCount];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(pageCount)] pageCount];
NSUInteger currentPage = 1;
[[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThatBool([mockPaginator hasNextPage], is(equalToBool(YES)));
currentPage = 2;
[[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThatBool([mockPaginator hasNextPage], is(equalToBool(YES)));
currentPage = 3;
[[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThatBool([mockPaginator hasNextPage], is(equalToBool(NO)));
}
- (void)itShouldKnowIfItHasAPreviousPage {
- (void)testHasNextPageRaisesExpectionWhenNotLoaded {
RKObjectPaginator *paginator = [[RKObjectPaginator new] autorelease];
id mockPaginator = [OCMockObject partialMockForObject:paginator];
NSUInteger perPage = 5;
NSUInteger currentPage = 3;
NSUInteger objectCount = 10;
BOOL loaded = NO;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded];
STAssertThrows([mockPaginator hasNextPage], @"Expected exception due to isLoaded == NO");
}
- (void)testHasNextPageRaisesExpectionWhenPageCountIsUnknown {
RKObjectPaginator *paginator = [[RKObjectPaginator new] autorelease];
id mockPaginator = [OCMockObject partialMockForObject:paginator];
BOOL loaded = YES;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded];
BOOL hasPageCount = NO;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(hasPageCount)] hasPageCount];
STAssertThrows([mockPaginator hasNextPage], @"Expected exception due to pageCount == NSUIntegerMax");
}
- (void)testHasPreviousPageRaisesExpectionWhenNotLoaded {
RKObjectPaginator *paginator = [[RKObjectPaginator new] autorelease];
id mockPaginator = [OCMockObject partialMockForObject:paginator];
BOOL loaded = NO;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(loaded)] isLoaded];
STAssertThrows([mockPaginator hasPreviousPage], @"Expected exception due to isLoaded == NO");
}
- (void)testKnowledgeOfPreviousPage {
RKObjectPaginator *paginator = [[RKObjectPaginator new] autorelease];
id mockPaginator = [OCMockObject partialMockForObject:paginator];
BOOL isLoaded = YES;
NSUInteger perPage = 5;
NSUInteger pageCount = 3;
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(isLoaded)] isLoaded];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(perPage)] perPage];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(objectCount)] objectCount];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
[[[mockPaginator stub] andReturnValue:OCMOCK_VALUE(pageCount)] pageCount];
NSUInteger currentPage = 3;
[[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(YES)));
currentPage = 2;
[[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(YES)));
currentPage = 1;
[[[mockPaginator expect] andReturnValue:OCMOCK_VALUE(currentPage)] currentPage];
assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(NO)));
}

View File

@@ -183,7 +183,7 @@ class RestKit::SpecServer < Sinatra::Base
status 200
content_type 'application/json'
per_page = 2
per_page = 3
total_entries = 6
current_page = params[:page].to_i
entries = []