diff --git a/Code/CoreData/RKConnectionDescription.h b/Code/CoreData/RKConnectionDescription.h index 56baa7cf82..6445130cd6 100644 --- a/Code/CoreData/RKConnectionDescription.h +++ b/Code/CoreData/RKConnectionDescription.h @@ -46,6 +46,28 @@ Any number of attribute pairs may be specified, but all values must match for the connection to be satisfied and the relationship's value to be set. + ### Connecting with Collection Values + + Connections can be established by a collection of values. For example, imagine that the previously described project representation has been extended to include a list of team members who are working on the project: + + { "project": + { "id": 12345, + "name": "My Project", + "userID": 1, + "teamMemberIDs": [1, 2, 3, 4] + } + } + + The 'teamMemberIDs' contains an array specifying the ID's of the `User` objects who are collaborating on the project, which corresponds to a to-many relationship named 'teamMembers' on the `Project` entity. In this case, the 'teamMemberIDs' could be mapped on to an `NSArray` or `NSSet` property on the `Project` entity and then connected: + + NSEntityDescription *projectEntity = [NSEntityDescription entityForName:@"Project" inManagedObjectContext:managedObjectContext]; + NSRelationshipDescription *teamMembers = [projectEntity relationshipsByName][@"teamMembers"]; // To many relationship for the `User` entity + RKConnectionDescription *connection = [[RKConnectionDescription alloc] initWithRelationship:relationship attributes:@{ @"teamMemberIDs": @"userID" }]; + + When evaluating the above JSON, the connection would be established for the 'teamMembers' relationship to the `User` entities whose userID's are 1, 2, 3 or 4. + + Note that collections of attribute values are always interpetted as logic OR's, but compound connections are aggregated as a logical AND. For example, if we were to add a second connecting attribute for the "gender" property and include `"gender": "male"` in the JSON, the connection would be made to all `User` managed objects whose ID is 1, 2, 3, OR 4 AND whose gender is "male". + ## Key Path Connections A key path connection is established by evaluating the key path of the connection against the managed object being connected. The returned value has type transformation applied and is then assigned to the relationship. diff --git a/Code/CoreData/RKEntityByAttributeCache.h b/Code/CoreData/RKEntityByAttributeCache.h index 514f37d931..06f3faf7bc 100644 --- a/Code/CoreData/RKEntityByAttributeCache.h +++ b/Code/CoreData/RKEntityByAttributeCache.h @@ -153,7 +153,7 @@ @param context The managed object context to retrieve the objects from. @return An array of objects with the value of attribute matching attributeValue or an empty array. */ -- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context; +- (NSSet *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context; ///------------------------------ /// @name Managing Cached Objects diff --git a/Code/CoreData/RKEntityByAttributeCache.m b/Code/CoreData/RKEntityByAttributeCache.m index 05198761c9..6bec2fcd33 100644 --- a/Code/CoreData/RKEntityByAttributeCache.m +++ b/Code/CoreData/RKEntityByAttributeCache.m @@ -27,6 +27,7 @@ #import "RKPropertyInspector.h" #import "RKPropertyInspector+CoreData.h" #import "NSManagedObject+RKAdditions.h" +#import "RKObjectUtilities.h" // Set Logging Component #undef RKLogComponent @@ -54,6 +55,32 @@ static id RKCacheKeyValueForEntityAttributeWithValue(NSEntityDescription *entity return [sortedValues componentsJoinedByString:@":"]; } +/* + This function recursively calculates a set of cache keys given a dictionary of attribute values. The basic premise is that we wish to decompose all arrays of values within the dictionary into a distinct cache key, as each object within the cache will appear for only one key. + */ +static NSArray *RKCacheKeysForEntityFromAttributeValues(NSEntityDescription *entity, NSDictionary *attributeValues) +{ + NSMutableArray *cacheKeys = [NSMutableArray array]; + NSSet *collectionKeys = [attributeValues keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { + return RKObjectIsCollection(obj); + }]; + + if ([collectionKeys count] > 0) { + for (NSString *attributeName in collectionKeys) { + id attributeValue = [attributeValues objectForKey:attributeName]; + for (id value in attributeValue) { + NSMutableDictionary *mutableAttributeValues = [attributeValues mutableCopy]; + [mutableAttributeValues setValue:value forKey:attributeName]; + [cacheKeys addObjectsFromArray:RKCacheKeysForEntityFromAttributeValues(entity, mutableAttributeValues)]; + } + } + } else { + [cacheKeys addObject:RKCacheKeyForEntityWithAttributeValues(entity, attributeValues)]; + } + + return cacheKeys; +} + @interface RKEntityByAttributeCache () @property (nonatomic, strong) NSMutableDictionary *cacheKeysToObjectIDs; @end @@ -193,39 +220,39 @@ - (NSManagedObject *)objectForObjectID:(NSManagedObjectID *)objectID inContext:( - (NSManagedObject *)objectWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context { - NSArray *objects = [self objectsWithAttributeValues:attributeValues inContext:context]; - return ([objects count] > 0) ? [objects objectAtIndex:0] : nil; + NSSet *objects = [self objectsWithAttributeValues:attributeValues inContext:context]; + return ([objects count] > 0) ? [objects anyObject] : nil; } -- (NSArray *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context +- (NSSet *)objectsWithAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context { // TODO: Assert that the attribute values contains all of the cache attributes!!! - NSString *cacheKey = RKCacheKeyForEntityWithAttributeValues(self.entity, attributeValues); - NSArray *objectIDs = nil; - @synchronized(self.cacheKeysToObjectIDs) { - objectIDs = [[NSArray alloc] initWithArray:[self.cacheKeysToObjectIDs objectForKey:cacheKey] copyItems:YES]; - } - if ([objectIDs count]) { - /** - NOTE: - In my benchmarking, retrieving the objects one at a time using existingObjectWithID: is significantly faster - than issuing a single fetch request against all object ID's. - */ - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[objectIDs count]]; - for (NSManagedObjectID *objectID in objectIDs) { - NSManagedObject *object = [self objectForObjectID:objectID inContext:context]; - if (object) { - [objects addObject:object]; - } else { - RKLogDebug(@"Evicting objectID association for attributes %@ of Entity '%@': %@", attributeValues, self.entity.name, objectID); - [self removeObjectID:objectID forAttributeValues:attributeValues]; + NSMutableSet *objects = [NSMutableSet set]; + NSArray *cacheKeys = RKCacheKeysForEntityFromAttributeValues(self.entity, attributeValues); + for (NSString *cacheKey in cacheKeys) { + NSArray *objectIDs = nil; + @synchronized(self.cacheKeysToObjectIDs) { + objectIDs = [[NSArray alloc] initWithArray:[self.cacheKeysToObjectIDs objectForKey:cacheKey] copyItems:YES]; + } + if ([objectIDs count]) { + /** + NOTE: + In my benchmarking, retrieving the objects one at a time using existingObjectWithID: is significantly faster + than issuing a single fetch request against all object ID's. + */ + for (NSManagedObjectID *objectID in objectIDs) { + NSManagedObject *object = [self objectForObjectID:objectID inContext:context]; + if (object) { + [objects addObject:object]; + } else { + RKLogDebug(@"Evicting objectID association for attributes %@ of Entity '%@': %@", attributeValues, self.entity.name, objectID); + [self removeObjectID:objectID forAttributeValues:attributeValues]; + } } } - - return objects; } - return [NSArray array]; + return objects; } - (void)setObjectID:(NSManagedObjectID *)objectID forAttributeValues:(NSDictionary *)attributeValues diff --git a/Code/CoreData/RKEntityCache.h b/Code/CoreData/RKEntityCache.h index d063845867..d791b86dba 100644 --- a/Code/CoreData/RKEntityCache.h +++ b/Code/CoreData/RKEntityCache.h @@ -91,7 +91,7 @@ @return All matching managed object instances or nil. @raise NSInvalidArgumentException Raised if instances of the entity and attribute have not been cached. */ -- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context; +- (NSSet *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context; ///----------------------------------------------------------------------------- // @name Accessing Underlying Caches diff --git a/Code/CoreData/RKEntityCache.m b/Code/CoreData/RKEntityCache.m index 663f905a9e..90920277d5 100644 --- a/Code/CoreData/RKEntityCache.m +++ b/Code/CoreData/RKEntityCache.m @@ -81,7 +81,7 @@ - (NSManagedObject *)objectForEntity:(NSEntityDescription *)entity withAttribute return nil; } -- (NSArray *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context +- (NSSet *)objectsForEntity:(NSEntityDescription *)entity withAttributeValues:(NSDictionary *)attributeValues inContext:(NSManagedObjectContext *)context { NSParameterAssert(entity); NSParameterAssert(attributeValues); diff --git a/Code/CoreData/RKEntityMapping.m b/Code/CoreData/RKEntityMapping.m index c88b41a762..3a4a763283 100644 --- a/Code/CoreData/RKEntityMapping.m +++ b/Code/CoreData/RKEntityMapping.m @@ -36,7 +36,7 @@ static NSArray *RKEntityIdentificationAttributesFromUserInfoOfEntity(NSEntityDescription *entity) { - id userInfoValue = [entity userInfo][RKEntityIdentificationAttributesUserInfoKey]; + id userInfoValue = [[entity userInfo] valueForKey:RKEntityIdentificationAttributesUserInfoKey]; if (userInfoValue) { NSArray *attributeNames = [userInfoValue isKindOfClass:[NSArray class]] ? userInfoValue : @[ userInfoValue ]; NSMutableArray *attributes = [NSMutableArray arrayWithCapacity:[attributeNames count]]; @@ -270,6 +270,19 @@ - (Class)classForProperty:(NSString *)propertyName return propertyClass; } +- (Class)classForKeyPath:(NSString *)keyPath +{ + NSArray *components = [keyPath componentsSeparatedByString:@"."]; + Class propertyClass = self.objectClass; + for (NSString *property in components) { + propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofClass:propertyClass]; + if (! propertyClass) propertyClass = [[RKPropertyInspector sharedInspector] classForPropertyNamed:property ofEntity:self.entity]; + if (! propertyClass) break; + } + + return propertyClass; +} + + (void)setEntityIdentificationInferenceEnabled:(BOOL)enabled { entityIdentificationInferenceEnabled = enabled; diff --git a/Code/CoreData/RKFetchRequestManagedObjectCache.m b/Code/CoreData/RKFetchRequestManagedObjectCache.m index f8e82e2242..aa351c0865 100644 --- a/Code/CoreData/RKFetchRequestManagedObjectCache.m +++ b/Code/CoreData/RKFetchRequestManagedObjectCache.m @@ -10,24 +10,33 @@ #import "RKLog.h" #import "RKPropertyInspector.h" #import "RKPropertyInspector+CoreData.h" +#import "RKObjectUtilities.h" // Set Logging Component #undef RKLogComponent #define RKLogComponent RKlcl_cRestKitCoreData +/* + NOTE: At the moment this cache key assume that the structure of the values for each key in the `attributeValues` in constant + i.e. if you have `userID`, it will always be a single value, or `userIDs` will always be an array. + It will need to be reimplemented if changes in attribute values occur during the life of a single cache + */ static NSString *RKPredicateCacheKeyForAttributes(NSArray *attributeNames) { return [[attributeNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] componentsJoinedByString:@":"]; } // NOTE: We build a dynamic format string here because `NSCompoundPredicate` does not support use of substiution variables -static NSPredicate *RKPredicateWithSubsitutionVariablesForAttributes(NSArray *attributeNames) +static NSPredicate *RKPredicateWithSubsitutionVariablesForAttributeValues(NSDictionary *attributeValues) { + NSArray *attributeNames = [attributeValues allKeys]; NSMutableArray *formatFragments = [NSMutableArray arrayWithCapacity:[attributeNames count]]; - for (NSString *attributeName in attributeNames) { - NSString *formatFragment = [NSString stringWithFormat:@"%@ = $%@", attributeName, attributeName]; + [attributeValues enumerateKeysAndObjectsUsingBlock:^(NSString *attributeName, id value, BOOL *stop) { + NSString *formatFragment = RKObjectIsCollection(value) + ? [NSString stringWithFormat:@"%@ IN $%@", attributeName, attributeName] + : [NSString stringWithFormat:@"%@ = $%@", attributeName, attributeName]; [formatFragments addObject:formatFragment]; - } + }]; return [NSPredicate predicateWithFormat:[formatFragments componentsJoinedByString:@" AND "]]; } @@ -47,9 +56,9 @@ - (id)init return self; } -- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity - attributeValues:(NSDictionary *)attributeValues - inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext +- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity + attributeValues:(NSDictionary *)attributeValues + inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext { NSAssert(entity, @"Cannot find existing managed object without a target class"); @@ -59,7 +68,7 @@ - (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity NSString *predicateCacheKey = RKPredicateCacheKeyForAttributes([attributeValues allKeys]); NSPredicate *substitutionPredicate = [self.predicateCache objectForKey:predicateCacheKey]; if (! substitutionPredicate) { - substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributes([attributeValues allKeys]); + substitutionPredicate = RKPredicateWithSubsitutionVariablesForAttributeValues(attributeValues); [self.predicateCache setObject:substitutionPredicate forKey:predicateCacheKey]; } @@ -72,7 +81,7 @@ - (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity } RKLogDebug(@"Found objects '%@' using fetchRequest '%@'", objects, fetchRequest); - return objects; + return [NSSet setWithArray:objects]; } @end diff --git a/Code/CoreData/RKInMemoryManagedObjectCache.m b/Code/CoreData/RKInMemoryManagedObjectCache.m index 80e1634204..bb99ba5872 100644 --- a/Code/CoreData/RKInMemoryManagedObjectCache.m +++ b/Code/CoreData/RKInMemoryManagedObjectCache.m @@ -51,7 +51,7 @@ - (id)init userInfo:nil]; } -- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity +- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity attributeValues:(NSDictionary *)attributeValues inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext { diff --git a/Code/CoreData/RKManagedObjectCaching.h b/Code/CoreData/RKManagedObjectCaching.h index 4637e27568..2fcfcadc48 100644 --- a/Code/CoreData/RKManagedObjectCaching.h +++ b/Code/CoreData/RKManagedObjectCaching.h @@ -38,9 +38,9 @@ @param attributeValues A dictionary specifying the attribute criteria for retrieving managed objects. @param managedObjectContext The context to fetch the matching objects in. */ -- (NSArray *)managedObjectsWithEntity:(NSEntityDescription *)entity - attributeValues:(NSDictionary *)attributeValues - inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext; +- (NSSet *)managedObjectsWithEntity:(NSEntityDescription *)entity + attributeValues:(NSDictionary *)attributeValues + inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext; ///--------------------------------------------------- /// @name Handling Managed Object Change Notifications diff --git a/Code/CoreData/RKManagedObjectMappingOperationDataSource.m b/Code/CoreData/RKManagedObjectMappingOperationDataSource.m index ad00313f29..9e5c65cbfd 100644 --- a/Code/CoreData/RKManagedObjectMappingOperationDataSource.m +++ b/Code/CoreData/RKManagedObjectMappingOperationDataSource.m @@ -161,12 +161,12 @@ - (id)mappingOperation:(RKMappingOperation *)mappingOperation targetObjectForRep NSEntityDescription *entity = [entityMapping entity]; NSManagedObject *managedObject = nil; if ([entityIdentifierAttributes count]) { - NSArray *objects = [self.managedObjectCache managedObjectsWithEntity:entity - attributeValues:entityIdentifierAttributes - inManagedObjectContext:self.managedObjectContext]; - if (entityMapping.identificationPredicate) objects = [objects filteredArrayUsingPredicate:entityMapping.identificationPredicate]; + NSSet *objects = [self.managedObjectCache managedObjectsWithEntity:entity + attributeValues:entityIdentifierAttributes + inManagedObjectContext:self.managedObjectContext]; + if (entityMapping.identificationPredicate) objects = [objects filteredSetUsingPredicate:entityMapping.identificationPredicate]; if ([objects count] > 0) { - managedObject = objects[0]; + managedObject = [objects anyObject]; if ([objects count] > 1) RKLogWarning(@"Managed object cache returned %ld objects for the identifier configured for the '%@' entity, expected 1.", (long) [objects count], [entity name]); } if (managedObject && [self.managedObjectCache respondsToSelector:@selector(didFetchObject:)]) { diff --git a/Code/CoreData/RKManagedObjectStore.m b/Code/CoreData/RKManagedObjectStore.m index 334855c61e..00cdf6a4f0 100644 --- a/Code/CoreData/RKManagedObjectStore.m +++ b/Code/CoreData/RKManagedObjectStore.m @@ -212,6 +212,9 @@ - (void)recreateManagedObjectContexts - (BOOL)resetPersistentStores:(NSError **)error { + [self.mainQueueManagedObjectContext reset]; + [self.persistentStoreManagedObjectContext reset]; + NSError *localError; for (NSPersistentStore *persistentStore in self.persistentStoreCoordinator.persistentStores) { NSURL *URL = [self.persistentStoreCoordinator URLForPersistentStore:persistentStore]; diff --git a/Code/CoreData/RKRelationshipConnectionOperation.m b/Code/CoreData/RKRelationshipConnectionOperation.m index f8416931f0..6a25de9b28 100644 --- a/Code/CoreData/RKRelationshipConnectionOperation.m +++ b/Code/CoreData/RKRelationshipConnectionOperation.m @@ -140,15 +140,15 @@ - (id)findConnected id connectionResult = nil; if ([self.connection isForeignKeyConnection]) { NSDictionary *attributeValues = RKConnectionAttributeValuesWithObject(self.connection, self.managedObject); - NSArray *managedObjects = [self.managedObjectCache managedObjectsWithEntity:[self.connection.relationship destinationEntity] - attributeValues:attributeValues - inManagedObjectContext:self.managedObjectContext]; - if (self.connection.predicate) managedObjects = [managedObjects filteredArrayUsingPredicate:self.connection.predicate]; + NSSet *managedObjects = [self.managedObjectCache managedObjectsWithEntity:[self.connection.relationship destinationEntity] + attributeValues:attributeValues + inManagedObjectContext:self.managedObjectContext]; + if (self.connection.predicate) managedObjects = [managedObjects filteredSetUsingPredicate:self.connection.predicate]; if ([self.connection.relationship isToMany]) { connectionResult = managedObjects; } else { - if ([managedObjects count] > 1) RKLogWarning(@"Retrieved %ld objects satisfying connection criteria for one-to-one relationship connection: only the first result will be connected.", (long) [managedObjects count]); - if ([managedObjects count]) connectionResult = managedObjects[0]; + if ([managedObjects count] > 1) RKLogWarning(@"Retrieved %ld objects satisfying connection criteria for one-to-one relationship connection: only object will be connected.", (long) [managedObjects count]); + if ([managedObjects count]) connectionResult = [managedObjects anyObject]; } } else if ([self.connection isKeyPathConnection]) { connectionResult = [self.managedObject valueForKeyPath:self.connection.keyPath]; diff --git a/Code/Network/RKManagedObjectRequestOperation.h b/Code/Network/RKManagedObjectRequestOperation.h index 285518e7c4..9b1863117b 100644 --- a/Code/Network/RKManagedObjectRequestOperation.h +++ b/Code/Network/RKManagedObjectRequestOperation.h @@ -67,7 +67,7 @@ airportID = [argsDict objectForKey:@"airport_id"]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Terminal"]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Airport" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; - fetchRequest.predicate = [entity predicateForPrimaryKeyAttributeWithValue:airportID]; + fetchRequest.predicate = [NSPredication predicateWithFormat:@"airportID = %@", @([airportID integerValue])]; // NOTE: Coerced from string to number fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES] ]; return fetchRequest; } @@ -75,7 +75,7 @@ return nil; }]; - The above example code defines an `RKFetchRequestBlock` block object that will match an `NSURL` with a relative path matching the pattern `@"/airports/:airport_id/terminals.json"`. If a match is found, the block extracts the `airport_id` key from the matched arguments and uses it to construct an `NSPredicate` for the primary key attribute of `GGAirport` entity. It then constructs an `NSFetchRequest` for the `GGTerminal` entity that will retrieve all the managed objects with an airport ID attribute whose value is equal to `airport_id` encoded within the URL's path. + The above example code defines an `RKFetchRequestBlock` block object that will match an `NSURL` with a relative path matching the pattern `@"/airports/:airport_id/terminals.json"`. If a match is found, the block extracts the `airport_id` key from the matched arguments, coerces its value into a number, and uses it to construct an `NSPredicate` for the primary key attribute of `GGAirport` entity. Take note that the value of the 'airport_id' was coerced from an `NSString` to an `NSNumber` -- failure to so would result in a predicate whose value is equal to `airportID == '1234'` vs. `airportID == 1234`, which will prevent fetch requests from evaluating correctly. Once coerced, the value is used to construct a `NSFetchRequest` object for the `GGTerminal` entity that will retrieve all the managed objects with an airport ID attribute whose value is equal to `airport_id` encoded within the URL's path. In more concrete terms, given the URL `http://restkit.org/airports/1234/terminals.json` the block would return an `NSFetchRequest` equal to: diff --git a/Code/Network/RKObjectManager.h b/Code/Network/RKObjectManager.h index 8007d1cffe..26de59cedf 100644 --- a/Code/Network/RKObjectManager.h +++ b/Code/Network/RKObjectManager.h @@ -36,10 +36,22 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor; Object request operations model the lifecycle of an object mapped HTTP request from start to finish. They are initialized with a fully configured `NSURLRequest` object and a set of `RKResponseDescriptor` objects that specify how an HTTP response is to be mapped into local domain objects. Object request operations may be constructed as standalone objects, but are often constructed through an `RKObjectManager` object. The object request operation encapsulates the functionality of two underlying operations that perform the bulk of the work. The HTTP request and response loading is handled by an `RKHTTPRequestOperation`, which is responsible for the HTTP transport details. Once a response has been successfully loaded, the object request operation starts an `RKResponseMapperOperation` that is responsible for handling the mapping of the response body. When working with Core Data, the `RKManagedObjectRequestOperation` class is used. The object manager encapsulates the Core Data configuration details and provides an interface that will return the appropriate object request operation for a request through the `appropriateObjectRequestOperationWithObject:method:path:parameters:` method. - ## The Base URL and Path Patterns + ## Base URL, Relative Paths and Path Patterns Each object manager is configured with a base URL that defines the URL that all request sent through the manager will be relative to. The base URL is configured directly through the `managerWithBaseURL:` method or is inherited from an AFNetworking `AFHTTPClient` object if the manager is initialized via the `initWithHTTPClient:` method. The base URL can point directly at the root of a URL or may include a path. + Many of the methods of the object manager accept a path argument, either directly or in the form of a path pattern. Whenever a path is provided to the object manager directly, as part of a request or response descriptor (see "Request and Response Descriptors"), or via a route (see the "Routing" section), the path is used to construct an `NSURL` object with `[NSURL URLWithString:relativeToURL:]`. The rules for the evaluation of a relative URL can at times be surprising and many configuration errors result from incorrectly configuring the `baseURL` and relative paths thereof. For reference, here are some examples borrowed from the AFNetworking documentation detailing how base URL's and relative paths interact: + + NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; + [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo + [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz + [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo + [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo + [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ + [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ + + Keep these rules in mind when providing relative paths to the object manager. + Path patterns are a common unit of abstraction in RestKit for describing the path portion of URL's. When working with API's, there is typically one or more dynamic portions of the URL that correspond to primary keys or other identifying resource attributes. For example, a blogging application may represent articles in a URL structure such as '/articles/1234' and comments about an article might appear at '/articles/1234/comments'. These path structures could be represented as the path patterns '/articles/:articleID' and '/articles/:articleID/comments', substituing the dynamic key ':articleID' in place of the primary key of in the path. These keys can be used to interpolate a path with an object's property values using key-value coding or be used to match a string. Path patterns appear throughout RestKit, but the most fundamental uses are for the dynamic generation of URL paths from objects and the matching of request and response URLs for mapping configuration. When generating a URL, a path pattern is interpolated with the value of an object. Consider this example: @@ -255,7 +267,7 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor; /** The AFNetworking HTTP client with which the receiver makes requests. */ -@property (nonatomic, strong, readonly) AFHTTPClient *HTTPClient; +@property (nonatomic, strong, readwrite) AFHTTPClient *HTTPClient; /** The base URL of the underlying HTTP client. diff --git a/Code/Network/RKObjectManager.m b/Code/Network/RKObjectManager.m index 413edb1a00..54834dfc85 100644 --- a/Code/Network/RKObjectManager.m +++ b/Code/Network/RKObjectManager.m @@ -31,6 +31,8 @@ #import "RKPathMatcher.h" #import "RKMappingErrors.h" #import "RKPaginator.h" +#import "RKDynamicMapping.h" +#import "RKRelationshipMapping.h" #if !__has_feature(objc_arc) #error RestKit must be built with ARC. @@ -81,17 +83,87 @@ } /** - Returns `YES` if the given array of `RKResponseDescriptor` objects contains an `RKEntityMapping`. + Visits all mappings accessible via relationships or dynamic mapping in an object graph starting from a given mapping. + */ +@interface RKMappingGraphVisitor : NSObject + +@property (nonatomic, readonly) NSSet *mappings; + +- (id)initWithMapping:(RKMapping *)mapping; + +@end + +@interface RKMappingGraphVisitor () +@property (nonatomic, readwrite) NSMutableSet *mutableMappings; +@end + +@implementation RKMappingGraphVisitor + +- (id)initWithMapping:(RKMapping *)mapping +{ + self = [super init]; + if (self) { + self.mutableMappings = [NSMutableSet set]; + [self visitMapping:mapping]; + } + return self; +} + +- (NSSet *)mappings +{ + return self.mutableMappings; +} + +- (void)visitMapping:(RKMapping *)mapping +{ + if ([self.mappings containsObject:mapping]) return; + [self.mutableMappings addObject:mapping]; + + if ([mapping isKindOfClass:[RKDynamicMapping class]]) { + RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping; + for (RKMapping *nestedMapping in dynamicMapping.objectMappings) { + [self visitMapping:nestedMapping]; + } + } else if ([mapping isKindOfClass:[RKObjectMapping class]]) { + RKObjectMapping *objectMapping = (RKObjectMapping *)mapping; + for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) { + [self visitMapping:relationshipMapping.mapping]; + } + } +} + +@end + +/** + Returns `YES` if the given array of `RKResponseDescriptor` objects contains an `RKEntityMapping` anywhere in its object graph. @param responseDescriptor An array of `RKResponseDescriptor` objects. @return `YES` if the `mapping` property of any of the response descriptor objects in the given array is an instance of `RKEntityMapping`, else `NO`. */ static BOOL RKDoesArrayOfResponseDescriptorsContainEntityMapping(NSArray *responseDescriptors) { + // Visit all mappings accessible from the object graphs of all response descriptors + NSMutableSet *accessibleMappings = [NSMutableSet set]; for (RKResponseDescriptor *responseDescriptor in responseDescriptors) { - if ([responseDescriptor.mapping isKindOfClass:[RKEntityMapping class]]) { + if (! [accessibleMappings containsObject:responseDescriptor.mapping]) { + RKMappingGraphVisitor *graphVisitor = [[RKMappingGraphVisitor alloc] initWithMapping:responseDescriptor.mapping]; + [accessibleMappings unionSet:graphVisitor.mappings]; + } + } + + // Enumerate all mappings and search for an `RKEntityMapping` + for (RKMapping *mapping in accessibleMappings) { + if ([mapping isKindOfClass:[RKEntityMapping class]]) { return YES; } + + if ([mapping isKindOfClass:[RKDynamicMapping class]]) { + RKDynamicMapping *dynamicMapping = (RKDynamicMapping *)mapping; + if ([dynamicMapping.objectMappings count] == 0) { + // Likely means that there is a representation block, assume `YES` + return YES; + } + } } return NO; @@ -122,7 +194,6 @@ static BOOL RKDoesArrayOfResponseDescriptorsContainEntityMapping(NSArray *respon /////////////////////////////////// @interface RKObjectManager () -@property (nonatomic, strong, readwrite) AFHTTPClient *HTTPClient; @property (nonatomic, strong) NSMutableArray *mutableRequestDescriptors; @property (nonatomic, strong) NSMutableArray *mutableResponseDescriptors; @property (nonatomic, strong) NSMutableArray *mutableFetchRequestBlocks; diff --git a/Code/Network/RKPaginator.h b/Code/Network/RKPaginator.h index 53d10ca027..ada67ed97b 100644 --- a/Code/Network/RKPaginator.h +++ b/Code/Network/RKPaginator.h @@ -24,12 +24,27 @@ #import "RKMappingResult.h" /** - Instances of RKPaginator retrieve paginated collections of mappable data - from remote systems via HTTP. Paginators perform GET requests and use a patterned - URL to construct a full URL reflecting the state of the paginator. Paginators rely - on an instance of RKObjectMappingProvider to determine how to perform object mapping - on the retrieved data. Paginators can load Core Data backed models provided that an - instance of RKManagedObjectStore is assigned to the paginator. + Instances of `RKPaginator` retrieve paginated collections of mappable data from remote systems via HTTP. Paginators perform GET requests and use a patterned URL to construct a full URL reflecting the state of the paginator. Paginators rely on an instance of RKObjectMappingProvider to determine how to perform object mapping on the retrieved data. Paginators can load Core Data backed models provided that an instance of RKManagedObjectStore is assigned to the paginator. + + ## Configuring Pagination Mapping + + The paginator must be configured with a `paginationMapping` specifying how configuration metadata is to be mapped out of the response payload. The configured mapping must have an `objectClass` of `RKPaginator` and should include attribute mappings for the `currentPage`, `pageCount`, `perPage`, and `objectCount`. For example, given a paginated resource loaded from '/articles?page=1' with the followibg JSON: + + { "pagination": { "per_page": 10, "total_pages": 25, "total_objects": 250 }, "articles": [ // Array of articles ] } + + The pagination mapping would be configured as: + + RKObjectMapping *paginationMapping = [RKObjectMapping mappingForClass:[RKPaginator class]]; + [paginationMapping addAttributeMappingsFromDictionary:@{ + @"pagination.per_page", @"perPage", + @"pagination.total_pages", @"pageCount", + @"pagination.total_objects", @"objectCount", + }]; + + ## iOS 5 Compatibility Caveats + + The paginator is compatible with iOS 5.x through the use of proxy attributes. In iOS 6.0 and greater, key-value coding supports the automatic boxing and unboxing of primitive values. This enables direct mapping configuration for the `currentPage`, `pageCount`, `perPage`, and `objectCount` attributes. Under iOS 5, where autoboxing is not available, mapping configuration must target special proxy attributes instead. For each of the above properties, a private `NSNumber` property is implemented by the class. Each proxy property has 'Number' appended as a suffix to the property name: `currentPageNumber`, `pageCountNumber`, `perPageNumber`, and `objectCountNumber`. + */ @interface RKPaginator : NSObject @@ -109,6 +124,8 @@ /** The object mapping defining how pagination metadata is to be mapped from a paginated response onto the paginator object. + See the documentation in the "Configuring Pagination Mapping" section for details about the pagination mapping. + @warning The `objectClass` of the given mapping must be `RKPaginator`. */ @property (nonatomic, strong) RKObjectMapping *paginationMapping; @@ -160,7 +177,7 @@ 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`. + @exception NSInternalInconsistencyException Raised if `hasPageCount` is `NO`. */ @property (nonatomic, readonly) NSUInteger pageCount; @@ -168,7 +185,7 @@ 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`. + @exception NSInternalInconsistencyException Raised if `hasObjectCount` is `NO`. */ @property (nonatomic, readonly) NSUInteger objectCount; diff --git a/Code/Network/RKPaginator.m b/Code/Network/RKPaginator.m index 47c98ce937..8894baae85 100644 --- a/Code/Network/RKPaginator.m +++ b/Code/Network/RKPaginator.m @@ -41,6 +41,12 @@ @interface RKPaginator () @property (nonatomic, strong, readwrite) RKMappingResult *mappingResult; @property (nonatomic, strong, readwrite) NSError *error; +// iOS 5.x compatible proxy attributes +@property (nonatomic, assign, readwrite) NSNumber *perPageNumber; +@property (nonatomic, assign, readwrite) NSNumber *currentPageNumber; +@property (nonatomic, assign, readwrite) NSNumber *pageCountNumber; +@property (nonatomic, assign, readwrite) NSNumber *objectCountNumber; + @property (nonatomic, copy) void (^successBlock)(RKPaginator *paginator, NSArray *objects, NSUInteger page); @property (nonatomic, copy) void (^failureBlock)(RKPaginator *paginator, NSError *error); @end @@ -239,4 +245,46 @@ - (void)cancel [self.objectRequestOperation cancel]; } +#pragma mark - iOS 5 proxy attributes + +- (NSNumber *)perPageNumber +{ + return [NSNumber numberWithUnsignedInteger:self.perPage]; +} + +- (void)setPerPageNumber:(NSNumber *)perPageNumber +{ + self.perPage = [perPageNumber unsignedIntegerValue]; +} + +- (NSNumber *)currentPageNumber +{ + return [NSNumber numberWithUnsignedInteger:self.currentPage]; +} + +- (void)setCurrentPageNumber:(NSNumber *)currentPageNumber +{ + self.currentPage = [currentPageNumber unsignedIntegerValue]; +} + +- (NSNumber *)pageCountNumber +{ + return [NSNumber numberWithUnsignedInteger:self.pageCount]; +} + +- (void)setPageCountNumber:(NSNumber *)pageCountNumber +{ + self.pageCount = [pageCountNumber unsignedIntegerValue]; +} + +- (NSNumber *)objectCountNumber +{ + return [NSNumber numberWithUnsignedInteger:self.objectCount]; +} + +- (void)setObjectCountNumber:(NSNumber *)objectCountNumber +{ + self.objectCount = [objectCountNumber unsignedIntegerValue]; +} + @end diff --git a/Code/Network/RKResponseDescriptor.m b/Code/Network/RKResponseDescriptor.m index f9e908ba68..b19621ce6b 100644 --- a/Code/Network/RKResponseDescriptor.m +++ b/Code/Network/RKResponseDescriptor.m @@ -23,7 +23,10 @@ #import "RKHTTPUtilities.h" // Cloned from AFStringFromIndexSet -- method should be non-static for reuse -static NSString *RKStringFromIndexSet(NSIndexSet *indexSet) { +NSString *RKStringFromIndexSet(NSIndexSet *indexSet); +NSString *RKStringFromIndexSet(NSIndexSet *indexSet) +{ + NSCParameterAssert(indexSet); NSMutableString *string = [NSMutableString string]; NSRange range = NSMakeRange([indexSet firstIndex], 1); @@ -80,7 +83,7 @@ + (RKResponseDescriptor *)responseDescriptorWithMapping:(RKMapping *)mapping - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p pathPattern=%@ keyPath=%@ statusCodes=%@ : %@>", - NSStringFromClass([self class]), self, self.pathPattern, self.keyPath, RKStringFromIndexSet(self.statusCodes), self.mapping]; + NSStringFromClass([self class]), self, self.pathPattern, self.keyPath, self.statusCodes ? RKStringFromIndexSet(self.statusCodes) : self.statusCodes, self.mapping]; } - (BOOL)matchesPath:(NSString *)path diff --git a/Code/Network/RKResponseMapperOperation.m b/Code/Network/RKResponseMapperOperation.m index d9c43f9548..c122c03cef 100644 --- a/Code/Network/RKResponseMapperOperation.m +++ b/Code/Network/RKResponseMapperOperation.m @@ -58,6 +58,39 @@ return [[NSError alloc] initWithDomain:RKErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo]; } +NSString *RKStringFromIndexSet(NSIndexSet *indexSet); // Defined in RKResponseDescriptor.m +static NSString *RKMatchFailureDescriptionForResponseDescriptorWithResponse(RKResponseDescriptor *responseDescriptor, NSHTTPURLResponse *response) +{ + if (responseDescriptor.statusCodes && ![responseDescriptor.statusCodes containsIndex:response.statusCode]) { + return [NSString stringWithFormat:@"response status code %ld is not within the range %@", (long) response.statusCode, RKStringFromIndexSet(responseDescriptor.statusCodes)]; + } + + NSString *pathAndQueryString = RKPathAndQueryStringFromURLRelativeToURL(response.URL, responseDescriptor.baseURL); + if (responseDescriptor.baseURL && !RKURLIsRelativeToURL(response.URL, responseDescriptor.baseURL)) { + // Not relative to the baseURL + return [NSString stringWithFormat:@"response URL '%@' is not relative to the baseURL '%@'.", response.URL, responseDescriptor.baseURL]; + } + + // Must be a path pattern mismatch + return [NSString stringWithFormat:@"response path '%@' did not match the path pattern '%@'.", pathAndQueryString, responseDescriptor.pathPattern]; +} + +static NSString *RKFailureReasonErrorStringForResponseDescriptorsMismatchWithResponse(NSArray *responseDescriptors, NSHTTPURLResponse *response) +{ + NSMutableString *failureReason = [NSMutableString string]; + [failureReason appendFormat:@"A %ld response was loaded from the URL '%@', which failed to match all (%ld) response descriptors:", + (long) response.statusCode, response.URL, (long) [responseDescriptors count]]; + + for (RKResponseDescriptor *responseDescriptor in responseDescriptors) { + [failureReason appendFormat:@"\n failed to match: %@", + responseDescriptor, responseDescriptor.baseURL, responseDescriptor.pathPattern, + responseDescriptor.statusCodes ? RKStringFromIndexSet(responseDescriptor.statusCodes) : responseDescriptor.statusCodes, + RKMatchFailureDescriptionForResponseDescriptorWithResponse(responseDescriptor, response)]; + } + + return failureReason; +} + /** A serial dispatch queue used for all deserialization of response bodies */ @@ -201,7 +234,7 @@ - (void)main self.error = error; return; } - if (self.isCancelled) return; + if (self.isCancelled) return; // Invoke the will map deserialized response block if (self.willMapDeserializedResponseBlock) { @@ -215,7 +248,7 @@ - (void)main } // Object map the response - self.mappingResult = [self performMappingWithObject:parsedBody error:&error]; + self.mappingResult = [self performMappingWithObject:parsedBody error:&error]; // If the response is a client error return either the mapping error or the mapped result to the caller as the error if (isClientError) { @@ -229,6 +262,18 @@ - (void)main return; } + // Fail if no response descriptors matched + if (error.code == RKMappingErrorNotFound && [self.responseMappingsDictionary count] == 0) { + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"No response descriptors match the response loaded.", nil), + NSLocalizedFailureReasonErrorKey: RKFailureReasonErrorStringForResponseDescriptorsMismatchWithResponse(self.responseDescriptors, self.response), + RKMappingErrorKeyPathErrorKey: [NSNull null], + NSURLErrorFailingURLErrorKey: self.response.URL, + NSURLErrorFailingURLStringErrorKey: [self.response.URL absoluteString], + NSUnderlyingErrorKey: error}; + self.error = [[NSError alloc] initWithDomain:RKErrorDomain code:RKMappingErrorNotFound userInfo:userInfo]; + return; + } + if (! self.mappingResult) { self.error = error; return; diff --git a/Code/Network/RKRouter.h b/Code/Network/RKRouter.h index 5e1e42637c..4bb3cdb4a4 100644 --- a/Code/Network/RKRouter.h +++ b/Code/Network/RKRouter.h @@ -106,7 +106,7 @@ /** The base URL that all URLs constructed by the receiver are relative to. */ -@property (nonatomic, strong, readonly) NSURL *baseURL; +@property (nonatomic, strong, readwrite) NSURL *baseURL; /** A route set defining all the routes addressable through the receiver. diff --git a/Code/Network/RKRouter.m b/Code/Network/RKRouter.m index dfbe52d43a..777f13d438 100644 --- a/Code/Network/RKRouter.m +++ b/Code/Network/RKRouter.m @@ -25,7 +25,6 @@ #import @interface RKRouter () -@property (nonatomic, strong, readwrite) NSURL *baseURL; @property (nonatomic, strong, readwrite) RKRouteSet *routeSet; @end diff --git a/Code/ObjectMapping.h b/Code/ObjectMapping.h index 9bff91a570..3f81814571 100644 --- a/Code/ObjectMapping.h +++ b/Code/ObjectMapping.h @@ -25,3 +25,4 @@ #import "RKObjectParameterization.h" #import "RKMappingResult.h" #import "RKMapperOperation.h" +#import "RKDynamicMapping.h" \ No newline at end of file diff --git a/Code/ObjectMapping/RKMapperOperation.m b/Code/ObjectMapping/RKMapperOperation.m index fd57b97188..dd8db29397 100644 --- a/Code/ObjectMapping/RKMapperOperation.m +++ b/Code/ObjectMapping/RKMapperOperation.m @@ -132,12 +132,18 @@ - (id)mapObject:(id)mappableObject atKeyPath:(NSString *)keyPath usingMapping:(R } else { NSAssert(objectMapping, @"Encountered unknown mapping type '%@'", NSStringFromClass([mapping class])); } + if (NO == [[self.targetObject class] isSubclassOfClass:objectMapping.objectClass]) { - 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:RKMappingErrorTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil]; - return nil; + if ([_mappingsDictionary count] == 1) { + 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:RKMappingErrorTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil]; + return nil; + } else { + // There is more than one mapping present. We are likely mapping secondary key paths to new objects + destinationObject = [self objectWithMapping:mapping andData:mappableObject]; + } } } else { destinationObject = [self objectWithMapping:mapping andData:mappableObject]; diff --git a/Code/Testing/RKTestFixture.h b/Code/Testing/RKTestFixture.h index 0428450e85..2790bf13e9 100644 --- a/Code/Testing/RKTestFixture.h +++ b/Code/Testing/RKTestFixture.h @@ -53,16 +53,6 @@ */ + (NSString *)pathForFixture:(NSString *)fixtureName; -#if TARGET_OS_IPHONE -/** - Creates and returns an image object by loading the image data from the fixture identified by the specified file name. - - @param fixtureName The name of the fixture file. - @return A new image object for the specified fixture, or nil if the method could not initialize the image from the specified file. - */ -//+ (UIImage *)imageWithContentsOfFixture:(NSString *)fixtureName; -#endif - /** Creates and returns a string object by reading data from the fixture identified by the specified file name using UTF-8 encoding. diff --git a/Code/Testing/RKTestFixture.m b/Code/Testing/RKTestFixture.m index 26287e3c2d..eda7a9ee1e 100644 --- a/Code/Testing/RKTestFixture.m +++ b/Code/Testing/RKTestFixture.m @@ -42,13 +42,6 @@ + (NSString *)pathForFixture:(NSString *)fixtureName return [[self fixtureBundle] pathForResource:fixtureName ofType:nil]; } -//#if TARGET_OS_IPHONE -//+ (UIImage *)imageWithContentsOfFixture:(NSString *)fixtureName -//{ -// return [[self fixtureBundle] imageWithContentsOfResource:fixtureName withExtension:nil]; -//} -//#endif - + (NSString *)stringWithContentsOfFixture:(NSString *)fixtureName { return [[self fixtureBundle] stringWithContentsOfResource:fixtureName withExtension:nil encoding:NSUTF8StringEncoding]; diff --git a/Examples/RKTwitterCoreData/Classes/RKTwitterAppDelegate.m b/Examples/RKTwitterCoreData/Classes/RKTwitterAppDelegate.m index 54241a784c..3ff177dad9 100644 --- a/Examples/RKTwitterCoreData/Classes/RKTwitterAppDelegate.m +++ b/Examples/RKTwitterCoreData/Classes/RKTwitterAppDelegate.m @@ -49,7 +49,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [userMapping addAttributeMappingsFromArray:@[ @"name" ]]; RKEntityMapping *tweetMapping = [RKEntityMapping mappingForEntityForName:@"Tweet" inManagedObjectStore:managedObjectStore]; - userMapping.identificationAttributes = @[ @"statusID" ]; + tweetMapping.identificationAttributes = @[ @"statusID" ]; [tweetMapping addAttributeMappingsFromDictionary:@{ @"id": @"statusID", @"created_at": @"createdAt", diff --git a/Rakefile b/Rakefile index 472c41d490..28956fc5f7 100644 --- a/Rakefile +++ b/Rakefile @@ -166,7 +166,7 @@ namespace :docs do run(command) if $?.exitstatus == 0 - command = "rsync -rvpPe ssh Docs/API/publish/*.xar #{destination}" + command = "rsync -rvpPe ssh Docs/API/publish/* #{destination}" run(command) end end diff --git a/RestKit.podspec b/RestKit.podspec index cdbf55ce4f..130a551d78 100644 --- a/RestKit.podspec +++ b/RestKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'RestKit' - s.version = '0.20.0pre2' + s.version = '0.20.0pre3' s.summary = 'RestKit is a framework for consuming and modeling RESTful web resources on iOS and OS X.' s.homepage = 'http://www.restkit.org' s.author = { 'Blake Watters' => 'blakewatters@gmail.com' } @@ -37,7 +37,7 @@ Pod::Spec.new do |s| ns.ios.frameworks = 'CFNetwork', 'Security', 'MobileCoreServices', 'SystemConfiguration' ns.osx.frameworks = 'CoreServices', 'Security', 'SystemConfiguration' ns.dependency 'SOCKit' - ns.dependency 'AFNetworking', '1.0' + ns.dependency 'AFNetworking', '1.0.1' ns.dependency 'RestKit/ObjectMapping' ns.dependency 'RestKit/Support' end diff --git a/RestKit.xcodeproj/project.pbxproj b/RestKit.xcodeproj/project.pbxproj index be286dbb12..85fecae787 100644 --- a/RestKit.xcodeproj/project.pbxproj +++ b/RestKit.xcodeproj/project.pbxproj @@ -321,6 +321,8 @@ 2534781715FFD4A6002C0E4E /* RKURLEncodedSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 2534781415FFD4A6002C0E4E /* RKURLEncodedSerialization.m */; }; 2534781815FFD4A6002C0E4E /* RKURLEncodedSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 2534781515FFD4A6002C0E4E /* RKURLEncodedSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2534781915FFD4A6002C0E4E /* RKURLEncodedSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 2534781515FFD4A6002C0E4E /* RKURLEncodedSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2536D1FD167270F100DF9BB0 /* RKRouterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2536D1FC167270F100DF9BB0 /* RKRouterTest.m */; }; + 2536D1FE167270F100DF9BB0 /* RKRouterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2536D1FC167270F100DF9BB0 /* RKRouterTest.m */; }; 253B495214E35D1A00B0483F /* RKTestFixture.h in Headers */ = {isa = PBXBuildFile; fileRef = 252EFB2014D9B35D004863C8 /* RKTestFixture.h */; settings = {ATTRIBUTES = (Public, ); }; }; 253B495F14E35EC300B0483F /* RKTestFixture.m in Sources */ = {isa = PBXBuildFile; fileRef = 252EFB2114D9B35D004863C8 /* RKTestFixture.m */; }; 254372A815F54995006E8424 /* RKObjectParameterization.h in Headers */ = {isa = PBXBuildFile; fileRef = 254372A615F54995006E8424 /* RKObjectParameterization.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -697,7 +699,6 @@ 25160F7B145657220060A5C5 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; 25160F7D1456572F0060A5C5 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; 25160FC71456F2330060A5C5 /* RKManagedObjectLoaderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKManagedObjectLoaderTest.m; sourceTree = ""; }; - 25160FC81456F2330060A5C5 /* RKManagedObjectMappingOperationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKManagedObjectMappingOperationTest.m; sourceTree = ""; }; 25160FC91456F2330060A5C5 /* RKEntityMappingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKEntityMappingTest.m; sourceTree = ""; }; 25160FCB1456F2330060A5C5 /* RKManagedObjectStoreTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKManagedObjectStoreTest.m; sourceTree = ""; }; 25160FCF1456F2330060A5C5 /* blake.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = blake.png; sourceTree = ""; }; @@ -799,6 +800,7 @@ 253477F615FFBD2E002C0E4E /* NSBundle+RKAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSBundle+RKAdditions.h"; path = "Testing/NSBundle+RKAdditions.h"; sourceTree = ""; }; 2534781415FFD4A6002C0E4E /* RKURLEncodedSerialization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKURLEncodedSerialization.m; sourceTree = ""; }; 2534781515FFD4A6002C0E4E /* RKURLEncodedSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKURLEncodedSerialization.h; sourceTree = ""; }; + 2536D1FC167270F100DF9BB0 /* RKRouterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRouterTest.m; sourceTree = ""; }; 254372A615F54995006E8424 /* RKObjectParameterization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKObjectParameterization.h; sourceTree = ""; }; 254372A715F54995006E8424 /* RKObjectParameterization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKObjectParameterization.m; sourceTree = ""; }; 254372AA15F54C3F006E8424 /* RKHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKHTTPRequestOperation.h; sourceTree = ""; }; @@ -1290,7 +1292,6 @@ isa = PBXGroup; children = ( 25160FC71456F2330060A5C5 /* RKManagedObjectLoaderTest.m */, - 25160FC81456F2330060A5C5 /* RKManagedObjectMappingOperationTest.m */, 25160FC91456F2330060A5C5 /* RKEntityMappingTest.m */, 25160FCB1456F2330060A5C5 /* RKManagedObjectStoreTest.m */, 25E36E0115195CED00F9E448 /* RKFetchRequestMappingCacheTest.m */, @@ -1446,6 +1447,7 @@ 259AC480162B05C80012D2F9 /* RKObjectRequestOperationTest.m */, 2549D645162B376F003DD135 /* RKRequestDescriptorTest.m */, 2548AC6C162F5E00009E79BF /* RKManagedObjectRequestOperationTest.m */, + 2536D1FC167270F100DF9BB0 /* RKRouterTest.m */, ); name = Network; path = Logic/Network; @@ -2339,6 +2341,7 @@ 255F87911656B22D00914D57 /* RKPaginatorTest.m in Sources */, 2546A95816628EDD0078E044 /* RKConnectionDescriptionTest.m in Sources */, 2543A25D1664FD3100821D5B /* RKResponseDescriptorTest.m in Sources */, + 2536D1FD167270F100DF9BB0 /* RKRouterTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2486,6 +2489,7 @@ 255F87921656B22F00914D57 /* RKPaginatorTest.m in Sources */, 2546A95916628EDD0078E044 /* RKConnectionDescriptionTest.m in Sources */, 2543A25E1664FD3200821D5B /* RKResponseDescriptorTest.m in Sources */, + 2536D1FE167270F100DF9BB0 /* RKRouterTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/Fixtures/JSON/SameKeyDifferentTargetClasses.json b/Tests/Fixtures/JSON/SameKeyDifferentTargetClasses.json index ddac615035..9cacae1459 100644 --- a/Tests/Fixtures/JSON/SameKeyDifferentTargetClasses.json +++ b/Tests/Fixtures/JSON/SameKeyDifferentTargetClasses.json @@ -1,5 +1,3 @@ -// TODO: Map this to a different class for each translation key - { "products": [ { "prodId": 5, diff --git a/Tests/Logic/CoreData/RKEntityByAttributeCacheTest.m b/Tests/Logic/CoreData/RKEntityByAttributeCacheTest.m index 00fd610852..909079afbe 100644 --- a/Tests/Logic/CoreData/RKEntityByAttributeCacheTest.m +++ b/Tests/Logic/CoreData/RKEntityByAttributeCacheTest.m @@ -155,11 +155,80 @@ - (void)testRetrievalOfObjectsWithAttributeValue NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.managedObjectContext; - NSArray *objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; + NSSet *objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; assertThat(objects, hasCountOf(2)); - assertThat([objects objectAtIndex:0], is(instanceOf([NSManagedObject class]))); + assertThat([objects anyObject], is(instanceOf([NSManagedObject class]))); } +- (void)testRetrievalOfObjectsWithCollectionAttributeValue +{ + RKHuman *human1 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext]; + human1.railsID = [NSNumber numberWithInteger:12345]; + RKHuman *human2 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext]; + human2.railsID = [NSNumber numberWithInteger:5678]; + [self.managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + [self.cache addObject:human1]; + [self.cache addObject:human2]; + + NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + childContext.parentContext = self.managedObjectContext; + NSSet *objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @[ @(12345), @(5678) ] } inContext:childContext]; + assertThat(objects, hasCountOf(2)); + assertThat([objects anyObject], is(instanceOf([NSManagedObject class]))); +} + +- (void)testRetrievalOfObjectsWithMoreThanOneCollectionAttributeValue +{ + NSEntityDescription *entity = [NSEntityDescription entityForName:@"Human" inManagedObjectContext:self.managedObjectContext]; + self.cache = [[RKEntityByAttributeCache alloc] initWithEntity:entity + attributes:@[ @"railsID", @"name" ] + managedObjectContext:self.managedObjectContext]; + // Disable cache monitoring. Tested in specific cases. + self.cache.monitorsContextForChanges = NO; + + RKHuman *human1 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext]; + human1.railsID = [NSNumber numberWithInteger:12345]; + human1.name = @"Blake"; + RKHuman *human2 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext]; + human2.railsID = [NSNumber numberWithInteger:5678]; + human2.name = @"Jeff"; + RKHuman *human3 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext]; + human3.railsID = [NSNumber numberWithInteger:9999]; + human3.name = @"Blake"; + [self.managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + [self.cache addObject:human1]; + [self.cache addObject:human2]; + [self.cache addObject:human3]; + + NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + childContext.parentContext = self.managedObjectContext; + NSSet *objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @[ @(12345), @(5678) ], @"name": @"Blake" } inContext:childContext]; + // should find human1 + assertThat(objects, hasCountOf(1)); + assertThat([objects anyObject], is(instanceOf([NSManagedObject class]))); + assertThat([[objects anyObject] objectID], equalTo([human1 objectID])); + + objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @[ @(12345), @(9999) ], @"name": @"Blake" } inContext:childContext]; + // should be human1 and human3 + assertThat(objects, hasCountOf(2)); + assertThat([objects valueForKey:@"objectID"], hasItems([human1 objectID], [human3 objectID], nil)); + + objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @[ @(12345), @(9999) ], @"name": @[ @"Blake", @"Jeff" ] } inContext:childContext]; + // should be human1, human2 and human3 + assertThat(objects, hasCountOf(2)); + assertThat([objects valueForKey:@"objectID"], hasItems([human1 objectID], [human3 objectID], nil)); + + objects = [self.cache objectsWithAttributeValues:@{ @"railsID": @[ @(31337), @(8888) ], @"name": @[ @"Blake", @"Jeff" ] } inContext:childContext]; + // should be none + assertThat(objects, hasCountOf(0)); +} + +// Do this with 3 attributes, 2 that are arrays and 1 that is not +// check +// Test blowing up if you request objects without enough cache keys + - (void)testAddingObjectToCache { RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext]; diff --git a/Tests/Logic/CoreData/RKEntityCacheTest.m b/Tests/Logic/CoreData/RKEntityCacheTest.m index 38117c3706..ed0d13d5a9 100644 --- a/Tests/Logic/CoreData/RKEntityCacheTest.m +++ b/Tests/Logic/CoreData/RKEntityCacheTest.m @@ -93,7 +93,7 @@ - (void)testRetrievalOfObjectsForEntityWithAttributeValue [_cache cacheObjectsForEntity:self.entity byAttributes:@[ @"railsID" ]]; NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.managedObjectStore.persistentStoreManagedObjectContext; - NSArray *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; + NSSet *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; assertThat(objects, hasCountOf(2)); assertThat([objects valueForKey:@"objectID"], containsInAnyOrder(human1.objectID, human2.objectID, nil)); } @@ -114,7 +114,7 @@ - (void)testThatFlushEmptiesAllUnderlyingAttributeCaches NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.managedObjectStore.persistentStoreManagedObjectContext; - NSArray *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; + NSSet *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; assertThat(objects, hasCountOf(2)); assertThat([objects valueForKey:@"objectID"], containsInAnyOrder(human1.objectID, human2.objectID, nil)); @@ -154,7 +154,7 @@ - (void)testAddingObjectAddsToEachUnderlyingEntityAttributeCaches NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.managedObjectStore.persistentStoreManagedObjectContext; - NSArray *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; + NSSet *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; assertThat(objects, hasCountOf(2)); assertThat([objects valueForKey:@"objectID"], containsInAnyOrder(human1.objectID, human2.objectID, nil)); @@ -188,7 +188,7 @@ - (void)testRemovingObjectRemovesFromUnderlyingEntityAttributeCaches NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = self.managedObjectStore.persistentStoreManagedObjectContext; - NSArray *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; + NSSet *objects = [self.cache objectsForEntity:self.entity withAttributeValues:@{ @"railsID": @(12345) } inContext:childContext]; assertThat(objects, hasCountOf(2)); assertThat([objects valueForKey:@"objectID"], containsInAnyOrder(human1.objectID, human2.objectID, nil)); diff --git a/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m b/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m index 7fa83b48e6..b9c31b265c 100644 --- a/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m +++ b/Tests/Logic/CoreData/RKFetchRequestMappingCacheTest.m @@ -30,10 +30,10 @@ - (void)testFetchRequestMappingCacheReturnsObjectsWithNumericPrimaryKey reginald.railsID = [NSNumber numberWithInt:123456]; [managedObjectStore.persistentStoreManagedObjectContext save:nil]; - NSArray *managedObjects = [cache managedObjectsWithEntity:entity + NSSet *managedObjects = [cache managedObjectsWithEntity:entity attributeValues:@{ @"railsID": @123456 } inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; - NSArray *cats = @[ reginald ]; + NSSet *cats = [NSSet setWithObject:reginald]; expect(managedObjects).to.equal(cats); } @@ -50,10 +50,10 @@ - (void)testFetchRequestMappingCacheReturnsObjectsWithStringPrimaryKey birthday.eventID = @"e-1234-a8-b12"; [managedObjectStore.persistentStoreManagedObjectContext save:nil]; - NSArray *managedObjects = [cache managedObjectsWithEntity:entity + NSSet *managedObjects = [cache managedObjectsWithEntity:entity attributeValues:@{ @"eventID": @"e-1234-a8-b12" } inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; - NSArray *birthdays = @[ birthday ]; + NSSet *birthdays = [NSSet setWithObject:birthday]; expect(managedObjects).to.equal(birthdays); } diff --git a/Tests/Logic/CoreData/RKManagedObjectMappingOperationDataSourceTest.m b/Tests/Logic/CoreData/RKManagedObjectMappingOperationDataSourceTest.m index a8747b8efb..b51be8e889 100644 --- a/Tests/Logic/CoreData/RKManagedObjectMappingOperationDataSourceTest.m +++ b/Tests/Logic/CoreData/RKManagedObjectMappingOperationDataSourceTest.m @@ -13,6 +13,11 @@ #import "RKManagedObjectMappingOperationDataSource.h" #import "RKMappableObject.h" #import "RKMappingErrors.h" +#import "RKCat.h" +#import "RKHuman.h" +#import "RKChild.h" +#import "RKParent.h" +//#import "RKBenchmark.h" @interface RKManagedObjectMappingOperationDataSourceTest : RKTestCase @@ -478,4 +483,598 @@ - (void)testEntityIdentifierWithPredicate expect(object).to.equal(human1); } +- (void)testMappingInPrivateQueue +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + managedObjectContext.parentContext = managedObjectStore.persistentStoreManagedObjectContext; + managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectContext + cache:managedObjectCache]; + + __block BOOL success; + __block NSError *error; + NSDictionary *sourceObject = @{ @"name" : @"Blake Watters" }; + [managedObjectContext performBlockAndWait:^{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + NSEntityDescription *entity = [NSEntityDescription entityForName:@"Human" inManagedObjectContext:managedObjectContext]; + RKHuman *human = [[RKHuman alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjectContext]; + RKMappingOperation *mappingOperation = [[RKMappingOperation alloc] initWithSourceObject:sourceObject destinationObject:human mapping:humanMapping]; + mappingOperation.dataSource = mappingOperationDataSource; + success = [mappingOperation performMapping:&error]; + + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.name, is(equalTo(@"Blake Watters"))); + }]; +} + +- (void)testShouldConnectRelationshipsByPrimaryKey +{ + /* Connect a new human to a cat */ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + [humanMapping addConnectionForRelationship:@"favoriteCat" connectedBy:@{ @"favoriteCatID": @"railsID" }]; + + // Create a cat to connect + RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + cat.name = @"Asia"; + cat.railsID = @31337; + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSDictionary *mappableData = @{ @"name": @"Blake", @"favoriteCatID": @31337 }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + mappingOperationDataSource.operationQueue = [NSOperationQueue new]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + [mappingOperationDataSource.operationQueue waitUntilAllOperationsAreFinished]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.favoriteCat, isNot(nilValue())); + assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); +} + +- (void)testShouldConnectRelationshipsByPrimaryKeyReverse +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + NSManagedObjectContext *managedObjectContext = managedObjectStore.persistentStoreManagedObjectContext; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name", @"railsID"]]; + [catMapping addConnectionForRelationship:@"favoriteOfHumans" connectedBy:@{ @"railsID": @"favoriteCatID" }]; + + // Create some humans to connect + RKHuman *blake = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + blake.name = @"Blake"; + blake.favoriteCatID = [NSNumber numberWithInt:31340]; + + RKHuman *jeremy = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + jeremy.name = @"Jeremy"; + jeremy.favoriteCatID = [NSNumber numberWithInt:31340]; + + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSDictionary *mappableData = @{ @"name": @"Asia", @"railsID": @31340 }; + RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + NSOperationQueue *operationQueue = [NSOperationQueue new]; + [operationQueue setSuspended:YES]; + mappingOperationDataSource.operationQueue = operationQueue; + __block BOOL success; + [managedObjectContext performBlockAndWait:^{ + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:cat mapping:catMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + success = [operation performMapping:&error]; + }]; + + [operationQueue setSuspended:NO]; + [operationQueue waitUntilAllOperationsAreFinished]; + + assertThatBool(success, is(equalToBool(YES))); + assertThat(cat.favoriteOfHumans, isNot(nilValue())); + assertThat([cat.favoriteOfHumans valueForKeyPath:@"name"], containsInAnyOrder(blake.name, jeremy.name, nil)); +} + +- (void)testConnectionOfHasManyRelationshipsByPrimaryKey +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + [humanMapping addConnectionForRelationship:@"favoriteCat" connectedBy:@{ @"favoriteCatID": @"railsID" }]; + + // Create a cat to connect + RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + cat.name = @"Asia"; + cat.railsID = @31337; + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSDictionary *mappableData = @{ @"name": @"Blake", @"favoriteCatID": @31337 }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + mappingOperationDataSource.operationQueue = [NSOperationQueue new]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + [mappingOperationDataSource.operationQueue waitUntilAllOperationsAreFinished]; + assertThat(human.favoriteCat, isNot(nilValue())); + assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); +} + +- (void)testConnectingARelationshipFromASourceAttributeWhoseValueIsACollectionWithFetchRequestCache +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"catIDs"]]; + [humanMapping addConnectionForRelationship:@"cats" connectedBy:@{ @"catIDs": @"railsID" }]; + + // Create a couple of cats to connect + RKCat *asia = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + asia.name = @"Asia"; + asia.railsID = @31337; + + RKCat *roy = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + roy.name = @"Reginald Royford Williams III"; + roy.railsID = [NSNumber numberWithInt:31338]; + + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSArray *catIDs = [NSArray arrayWithObjects:@31337, [NSNumber numberWithInt:31338], nil]; + NSDictionary *mappableData = @{ @"name": @"Blake", @"catIDs": catIDs }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + mappingOperationDataSource.operationQueue = [NSOperationQueue new]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + [mappingOperationDataSource.operationQueue waitUntilAllOperationsAreFinished]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.cats, isNot(nilValue())); + assertThat([human.cats valueForKeyPath:@"name"], containsInAnyOrder(@"Asia", @"Reginald Royford Williams III", nil)); +} + +- (void)testConnectingARelationshipFromASourceAttributeWhoseValueIsACollectionWithInMemoryCache +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"catIDs"]]; + [humanMapping addConnectionForRelationship:@"cats" connectedBy:@{ @"catIDs": @"railsID" }]; + + // Create a couple of cats to connect + RKCat *asia = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + asia.name = @"Asia"; + asia.railsID = @31337; + + RKCat *roy = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + roy.name = @"Reginald Royford Williams III"; + roy.railsID = [NSNumber numberWithInt:31338]; + + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSArray *catIDs = [NSArray arrayWithObjects:@31337, [NSNumber numberWithInt:31338], nil]; + NSDictionary *mappableData = @{ @"name": @"Blake", @"catIDs": catIDs }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + + RKInMemoryManagedObjectCache *managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + mappingOperationDataSource.operationQueue = [NSOperationQueue new]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + [mappingOperationDataSource.operationQueue waitUntilAllOperationsAreFinished]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.cats, isNot(nilValue())); + assertThat([human.cats valueForKeyPath:@"name"], containsInAnyOrder(@"Asia", @"Reginald Royford Williams III", nil)); +} + +- (void)testShouldConnectRelationshipsByPrimaryKeyWithDifferentSourceAndDestinationKeyPathsReverse +{ + /* Connect a new cat to a human */ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"railsID"]]; + + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name", @"humanId"]]; + [catMapping addConnectionForRelationship:@"human" connectedBy:@{ @"humanId": @"railsID" }]; + + // Create a human to connect + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + human.name = @"Blake"; + human.railsID = @31337; + [managedObjectStore.persistentStoreManagedObjectContext save:nil]; + + NSDictionary *mappableData = @{ @"name": @"Asia", @"humanId": @31337 }; + RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + mappingOperationDataSource.operationQueue = [NSOperationQueue new]; + RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:cat mapping:catMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + [mappingOperationDataSource.operationQueue waitUntilAllOperationsAreFinished]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(cat.human, isNot(nilValue())); + assertThat(cat.human.name, is(equalTo(@"Blake"))); +} + +- (void)testShouldLoadNestedHasManyRelationship +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + [humanMapping addRelationshipMappingWithSourceKeyPath:@"cats" mapping:catMapping]; + + NSArray *catsData = [NSArray arrayWithObject:[NSDictionary dictionaryWithObject:@"Asia" forKey:@"name"]]; + NSDictionary *mappableData = @{ @"name": @"Blake", @"favoriteCatID": @31337, @"cats": catsData }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); +} + +- (void)testShouldLoadOrderedHasManyRelationship +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + catMapping.identificationAttributes = @[ @"railsID" ]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + humanMapping.identificationAttributes = @[ @"railsID" ]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + [humanMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"cats" toKeyPath:@"catsInOrderByAge" withMapping:catMapping]];; + + NSArray *catsData = [NSArray arrayWithObject:[NSDictionary dictionaryWithObject:@"Asia" forKey:@"name"]]; + NSDictionary *mappableData = @{ @"name": @"Blake", @"favoriteCatID": @31337, @"cats": catsData }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat([human catsInOrderByAge], isNot(empty())); +} + +- (void)testShouldMapNullToAHasManyRelationship +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; + [catMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; + [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + [humanMapping addRelationshipMappingWithSourceKeyPath:@"cats" mapping:catMapping]; + + NSDictionary *mappableData = @{ @"name": @"Blake", @"cats": [NSNull null] }; + RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(human.cats, is(empty())); +} + +- (void)testShouldLoadNestedHasManyRelationshipWithoutABackingClass +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKEntityMapping *cloudMapping = [RKEntityMapping mappingForEntityForName:@"Cloud" inManagedObjectStore:managedObjectStore]; + [cloudMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *stormMapping = [RKEntityMapping mappingForEntityForName:@"Storm" inManagedObjectStore:managedObjectStore]; + [stormMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; + [stormMapping addRelationshipMappingWithSourceKeyPath:@"clouds" mapping:cloudMapping]; + + NSArray *cloudsData = [NSArray arrayWithObject:[NSDictionary dictionaryWithObject:@"Nimbus" forKey:@"name"]]; + NSDictionary *mappableData = @{ @"name": @"Hurricane", @"clouds": cloudsData }; + NSEntityDescription *entity = [NSEntityDescription entityForName:@"Storm" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + NSManagedObject *storm = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:storm mapping:stormMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); +} + +- (void)testShouldConnectManyToManyRelationships +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; + childMapping.identificationAttributes = @[ @"name" ]; + [childMapping addAttributeMappingsFromArray:@[@"name"]]; + + RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; + parentMapping.identificationAttributes = @[ @"railsID" ]; + [parentMapping addAttributeMappingsFromArray:@[@"name", @"age"]]; + [parentMapping addRelationshipMappingWithSourceKeyPath:@"children" mapping:childMapping]; + + NSArray *childMappableData = @[ @{ @"name": @"Maya" }, @{ @"name": @"Brady" } ]; + NSDictionary *parentMappableData = @{ @"name": @"Win", @"age": @34, @"children": childMappableData }; + RKParent *parent = [NSEntityDescription insertNewObjectForEntityForName:@"Parent" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + mappingOperationDataSource.operationQueue = [NSOperationQueue new]; + RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:parentMappableData destinationObject:parent mapping:parentMapping]; + operation.dataSource = mappingOperationDataSource; + NSError *error = nil; + BOOL success = [operation performMapping:&error]; + assertThatBool(success, is(equalToBool(YES))); + assertThat(parent.children, isNot(nilValue())); + assertThatUnsignedInteger([parent.children count], is(equalToInt(2))); + assertThat([[parent.children anyObject] parents], isNot(nilValue())); + assertThatBool([[[parent.children anyObject] parents] containsObject:parent], is(equalToBool(YES))); + assertThatUnsignedInteger([[[parent.children anyObject] parents] count], is(equalToInt(1))); +} + +- (void)testShouldConnectRelationshipsByPrimaryKeyRegardlessOfOrder +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + NSManagedObjectContext *managedObjectContext = managedObjectStore.persistentStoreManagedObjectContext; + + RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; + [parentMapping addAttributeMappingsFromArray:@[@"parentID"]]; + parentMapping.identificationAttributes = @[ @"parentID" ]; + + RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; + [childMapping addAttributeMappingsFromArray:@[@"fatherID"]]; + [childMapping addConnectionForRelationship:@"father" connectedBy:@{ @"fatherID": @"parentID" }]; + + NSDictionary *mappingsDictionary = @{ @"parents": parentMapping, @"children": childMapping }; + NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"ConnectingParents.json"]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectContext + cache:managedObjectCache]; + NSOperationQueue *operationQueue = [NSOperationQueue new]; + [operationQueue setSuspended:YES]; + mappingOperationDataSource.operationQueue = operationQueue; + [managedObjectContext performBlockAndWait:^{ + RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; + mapper.mappingOperationDataSource = mappingOperationDataSource; + [mapper start]; + }]; + + [operationQueue setSuspended:NO]; + [operationQueue waitUntilAllOperationsAreFinished]; + + [managedObjectContext performBlockAndWait:^{ + NSError *error; + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Parent"]; + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"parentID = %@", @1]; + fetchRequest.fetchLimit = 1; + NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; + RKParent *parent = [results lastObject]; + assertThat(parent, is(notNilValue())); + NSSet *children = [parent fatheredChildren]; + assertThat(children, hasCountOf(1)); + RKChild *child = [children anyObject]; + assertThat(child.father, is(notNilValue())); + }]; +} + +- (void)testMappingAPayloadContainingRepeatedObjectsDoesNotYieldDuplicatesWithFetchRequestMappingCache +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + managedObjectStore.managedObjectCache = managedObjectCache; + + RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; + childMapping.identificationAttributes = @[ @"childID" ]; + [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; + + RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; + [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; + parentMapping.identificationAttributes = @[ @"parentID" ]; + [parentMapping addRelationshipMappingWithSourceKeyPath:@"children" mapping:childMapping]; + + NSDictionary *mappingsDictionary = @{ @"parents": parentMapping }; + + NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"parents_and_children.json"]; + RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; + mapper.mappingOperationDataSource = mappingOperationDataSource; + [mapper start]; + + NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Parent" predicate:nil error:nil]; + NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Child" predicate:nil error:nil]; + assertThatInteger(parentCount, is(equalToInteger(2))); + assertThatInteger(childrenCount, is(equalToInteger(4))); +} + +- (void)testMappingAPayloadContainingRepeatedObjectsDoesNotYieldDuplicatesWithInMemoryMappingCache +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + RKInMemoryManagedObjectCache *managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext + cache:managedObjectCache]; + managedObjectStore.managedObjectCache = managedObjectCache; + + RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; + childMapping.identificationAttributes = @[ @"childID" ]; + [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; + + RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; + [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; + parentMapping.identificationAttributes = @[ @"parentID" ]; + [parentMapping addRelationshipMappingWithSourceKeyPath:@"children" mapping:childMapping]; + + // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary + // keys are not guaranteed to return in any particular order + NSDictionary *mappingsDictionary = @{ @"parents": parentMapping }; + + NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"parents_and_children.json"]; + RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; + mapper.mappingOperationDataSource = mappingOperationDataSource; + [mapper start]; + + NSError *error = nil; + BOOL success = [managedObjectStore.persistentStoreManagedObjectContext save:&error]; + assertThatBool(success, is(equalToBool(YES))); + NSLog(@"Failed to save MOC: %@", error); + assertThat(error, is(nilValue())); + + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Parent"]; + NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContext countForFetchRequest:fetchRequest error:&error]; + NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Child" predicate:nil error:nil]; + assertThatInteger(parentCount, is(equalToInteger(2))); + assertThatInteger(childrenCount, is(equalToInteger(4))); +} + +- (void)testConnectingToSubentitiesByFetchRequestCache +{ + +} + +- (void)testConnectingToSubentitiesByInMemoryCache +{ + +} + +// TODO: Import bencharmk utility somehow... +//- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithFetchRequestMappingCache +//{ +// RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; +// RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; +// RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext +// cache:managedObjectCache]; +// managedObjectStore.managedObjectCache = managedObjectCache; +// +// RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; +// childMapping.identificationAttributes = @[ @"childID" ]; +// [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; +// +// RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; +// [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; +// parentMapping.identificationAttributes = @[ @"parentID" ]; +// [parentMapping addRelationshipMappingWithSourceKeyPath:@"children" mapping:childMapping]; +// +// +// NSDictionary *mappingsDictionary = @{ @"parents": parentMapping }; +// NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"]; +// RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; +// mapper.mappingOperationDataSource = mappingOperationDataSource; +// +// RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelOff); +// RKLogConfigureByName("RestKit/CoreData", RKLogLevelOff); +// +// [RKBenchmark report:@"Mapping with Fetch Request Cache" executionBlock:^{ +// for (NSUInteger i = 0; i < 50; i++) { +// [mapper performMapping]; +// } +// }]; +// NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Parent" predicate:nil error:nil]; +// NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Child" predicate:nil error:nil]; +// assertThatInteger(parentCount, is(equalToInteger(25))); +// assertThatInteger(childrenCount, is(equalToInteger(51))); +//} +// +//- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithInMemoryMappingCache +//{ +// RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; +// RKInMemoryManagedObjectCache *managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; +// RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext +// cache:managedObjectCache]; +// managedObjectStore.managedObjectCache = managedObjectCache; +// +// RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; +// childMapping.identificationAttributes = @[ @"childID" ]; +// [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; +// +// RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; +// [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; +// parentMapping.identificationAttributes = @[ @"parentID" ]; +// [parentMapping addRelationshipMappingWithSourceKeyPath:@"children" mapping:childMapping]; +// +// NSDictionary *mappingsDictionary = @{ @"parents": parentMapping }; +// NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"]; +// RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; +// mapper.mappingOperationDataSource = mappingOperationDataSource; +// RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelOff); +// RKLogConfigureByName("RestKit/CoreData", RKLogLevelOff); +// +// [RKBenchmark report:@"Mapping with In Memory Cache" executionBlock:^{ +// for (NSUInteger i = 0; i < 50; i++) { +// [mapper performMapping]; +// } +// }]; +// NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Parent" predicate:nil error:nil]; +// NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContext countForEntityForName:@"Child" predicate:nil error:nil]; +// assertThatInteger(parentCount, is(equalToInteger(25))); +// assertThatInteger(childrenCount, is(equalToInteger(51))); +//} + @end diff --git a/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m b/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m deleted file mode 100644 index c3be3f2264..0000000000 --- a/Tests/Logic/CoreData/RKManagedObjectMappingOperationTest.m +++ /dev/null @@ -1,946 +0,0 @@ -// -// RKObjectMappingOperationTest.m -// RestKit -// -// Created by Blake Watters on 5/31/11. -// Copyright (c) 2009-2012 RestKit. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "RKTestEnvironment.h" -#import "RKEntityMapping.h" -#import "RKManagedObjectMappingOperationDataSource.h" -#import "RKCat.h" -#import "RKHuman.h" -#import "RKChild.h" -#import "RKParent.h" -#import "RKBenchmark.h" - -@interface RKManagedObjectMappingOperationTest : RKTestCase -@end - -@implementation RKManagedObjectMappingOperationTest - -- (void)setUp -{ - [RKTestFactory setUp]; -} - -- (void)tearDown -{ - [RKTestFactory tearDown]; -} - -- (void)testMappingInPrivateQueue -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - NSManagedObjectContext *managedObjectContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease]; - managedObjectContext.parentContext = managedObjectStore.persistentStoreManagedObjectContextContext; - managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectContext - cache:managedObjectCache]; - - __block BOOL success; - __block NSError *error; - NSDictionary *sourceObject = @{ @"name" : @"Blake Watters" }; - [managedObjectContext performBlockAndWait:^{ - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - NSEntityDescription *entity = [NSEntityDescription entityForName:@"Human" inManagedObjectContext:managedObjectContext]; - RKHuman *human = [[RKHuman alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjectContext]; - RKMappingOperation *mappingOperation = [RKMappingOperation mappingOperationFromObject:sourceObject toObject:human withMapping:humanMapping]; - mappingOperation.dataSource = mappingOperationDataSource; - success = [mappingOperation performMapping:&error]; - - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.name, is(equalTo(@"Blake Watters"))); - }]; -} - -- (void)testShouldConnectRelationshipsByPrimaryKey -{ - /* Connect a new human to a cat */ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping connectRelationship:@"favoriteCat" fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" withMapping:catMapping]; - - // Create a cat to connect - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, isNot(nilValue())); - assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); -} - -- (void)testShouldConnectRelationshipsByPrimaryKeyReverse -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - NSManagedObjectContext *managedObjectContext = managedObjectStore.persistentStoreManagedObjectContextContext; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name", @"railsID"]]; - [catMapping connectRelationship:@"favoriteOfHumans" fromKeyPath:@"railsID" toKeyPath:@"favoriteCatID" withMapping:humanMapping]; - - // Create some humans to connect - RKHuman *blake = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - blake.name = @"Blake"; - blake.favoriteCatID = [NSNumber numberWithInt:31340]; - - RKHuman *jeremy = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - jeremy.name = @"Jeremy"; - jeremy.favoriteCatID = [NSNumber numberWithInt:31340]; - - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Asia", @"railsID", [NSNumber numberWithInt:31340], nil]; - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - NSOperationQueue *operationQueue = [NSOperationQueue new]; - [operationQueue setSuspended:YES]; - mappingOperationDataSource.operationQueue = operationQueue; - __block BOOL success; - [managedObjectContext performBlockAndWait:^{ - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:cat mapping:catMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - success = [operation performMapping:&error]; - }]; - - [operationQueue setSuspended:NO]; - [operationQueue waitUntilAllOperationsAreFinished]; - - assertThatBool(success, is(equalToBool(YES))); - assertThat(cat.favoriteOfHumans, isNot(nilValue())); - assertThat([cat.favoriteOfHumans valueForKeyPath:@"name"], containsInAnyOrder(blake.name, jeremy.name, nil)); -} - -- (void)testConnectRelationshipsDoesNotLeakMemory -{ - RKManagedObjectStore* managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping connectRelationship:@"favoriteCat" fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" withMapping:catMapping]; - - // Create a cat to connect - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - mappingOperationDataSource.operationQueue = [[NSOperationQueue new] autorelease]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - [operation performMapping:&error]; - - assertThatInteger([operation retainCount], is(equalToInteger(1))); -} - -- (void)testConnectionOfHasManyRelationshipsByPrimaryKey -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping connectRelationship:@"favoriteCat" fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" withMapping:catMapping]; - - // Create a cat to connect - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, isNot(nilValue())); - assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); -} - -- (void)testShouldConnectRelationshipsByPrimaryKeyWithDifferentSourceAndDestinationKeyPaths -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"catIDs"]]; - [humanMapping connectRelationship:@"cats" fromKeyPath:@"catIDs" toKeyPath:@"railsID" withMapping:catMapping]; - - // Create a couple of cats to connect - RKCat *asia = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - asia.name = @"Asia"; - asia.railsID = [NSNumber numberWithInt:31337]; - - RKCat *roy = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - roy.name = @"Reginald Royford Williams III"; - roy.railsID = [NSNumber numberWithInt:31338]; - - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSArray *catIDs = [NSArray arrayWithObjects:[NSNumber numberWithInt:31337], [NSNumber numberWithInt:31338], nil]; - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"catIDs", catIDs, nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.cats, isNot(nilValue())); - assertThat([human.cats valueForKeyPath:@"name"], containsInAnyOrder(@"Asia", @"Reginald Royford Williams III", nil)); -} - -- (void)testShouldConnectRelationshipsByPrimaryKeyWithDifferentSourceAndDestinationKeyPathsReverse -{ - /* Connect a new cat to a human */ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"railsID"]]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name", @"humanId"]]; - [catMapping connectRelationship:@"human" fromKeyPath:@"humanId" toKeyPath:@"railsID" withMapping:humanMapping]; - - // Create a human to connect - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - human.name = @"Blake"; - human.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Asia", @"humanId", [NSNumber numberWithInt:31337], nil]; - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:cat mapping:catMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(cat.human, isNot(nilValue())); - assertThat(cat.human.name, is(equalTo(@"Blake"))); -} - -- (void)testShouldLoadNestedHasManyRelationship -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasMany:@"cats" withMapping:catMapping]; - - NSArray *catsData = [NSArray arrayWithObject:[NSDictionary dictionaryWithObject:@"Asia" forKey:@"name"]]; - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], @"cats", catsData, nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); -} - -- (void)testShouldLoadOrderedHasManyRelationship -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"cats" toKeyPath:@"catsInOrderByAge" withMapping:catMapping]];; - - NSArray *catsData = [NSArray arrayWithObject:[NSDictionary dictionaryWithObject:@"Asia" forKey:@"name"]]; - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], @"cats", catsData, nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat([human catsInOrderByAge], isNot(empty())); -} - -- (void)testShouldMapNullToAHasManyRelationship -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasMany:@"cats" withMapping:catMapping]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"cats", [NSNull null], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.cats, is(empty())); -} - -- (void)testShouldLoadNestedHasManyRelationshipWithoutABackingClass -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKEntityMapping *cloudMapping = [RKEntityMapping mappingForEntityForName:@"Cloud" inManagedObjectStore:managedObjectStore]; - [cloudMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *stormMapping = [RKEntityMapping mappingForEntityForName:@"Storm" inManagedObjectStore:managedObjectStore]; - [stormMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [stormMapping hasMany:@"clouds" withMapping:cloudMapping]; - - NSArray *cloudsData = [NSArray arrayWithObject:[NSDictionary dictionaryWithObject:@"Nimbus" forKey:@"name"]]; - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Hurricane", @"clouds", cloudsData, nil]; - NSEntityDescription *entity = [NSEntityDescription entityForName:@"Storm" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - NSManagedObject *storm = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:storm mapping:stormMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); -} - -- (void)testShouldDynamicallyConnectRelationshipsByPrimaryKeyWhenMatchingSucceeds -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping connectRelationship:@"favoriteCat" fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" withMapping:catMapping whenValueOfKeyPath:@"name" isEqualTo:@"Blake"]; - - // Create a cat to connect - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, isNot(nilValue())); - assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); -} - -- (void)testShouldNotDynamicallyConnectRelationshipsByPrimaryKeyWhenMatchingFails -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping *catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping connectRelationship:@"favoriteCat" fromKeyPath:@"favoriteCatID" toKeyPath:@"railsID" withMapping:catMapping whenValueOfKeyPath:@"name" isEqualTo:@"Jeff"]; - - // Create a cat to connect - RKCat *cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore.persistentStoreManagedObjectContextContext save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, is(nilValue())); -} - -- (void)testShouldConnectManyToManyRelationships -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - childmapping.identificationAttributes = @[ @"railsID" ]; - [childMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - parentmapping.identificationAttributes = @[ @"railsID" ]; - [parentMapping addAttributeMappingsFromArray:@[@"name", @"age"]]; - [parentMapping hasMany:@"children" withMapping:childMapping]; - - NSArray *childMappableData = [NSArray arrayWithObjects:[NSDictionary dictionaryWithKeysAndObjects:@"name", @"Maya", nil], - [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Brady", nil], nil]; - NSDictionary *parentMappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Win", - @"age", [NSNumber numberWithInt:34], - @"children", childMappableData, nil]; - RKParent *parent = [NSEntityDescription insertNewObjectForEntityForName:@"Parent" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:parentMappableData destinationObject:parent mapping:parentMapping]; - operation.dataSource = mappingOperationDataSource; - NSError *error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(parent.children, isNot(nilValue())); - assertThatUnsignedInteger([parent.children count], is(equalToInt(2))); - assertThat([[parent.children anyObject] parents], isNot(nilValue())); - assertThatBool([[[parent.children anyObject] parents] containsObject:parent], is(equalToBool(YES))); - assertThatUnsignedInteger([[[parent.children anyObject] parents] count], is(equalToInt(1))); -} - -- (void)testShouldConnectRelationshipsByPrimaryKeyRegardlessOfOrder -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - NSManagedObjectContext *managedObjectContext = managedObjectStore.persistentStoreManagedObjectContextContext; - - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - [parentMapping addAttributeMappingsFromArray:@[@"parentID"]]; - parentMapping.primaryKeyAttribute = @"parentID"; - - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - [childMapping addAttributeMappingsFromArray:@[@"fatherID"]]; - [childMapping connectRelationship:@"father" fromKeyPath:@"fatherID" toKeyPath:@"parentID" withMapping:parentMapping]; - - RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; - // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary - // keys are not guaranteed to return in any particular order - [mappingProvider setMapping:parentMapping forKeyPath:@"parents"]; - [mappingProvider setMapping:childMapping forKeyPath:@"children"]; - - NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"ConnectingParents.json"]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectContext - cache:managedObjectCache]; - NSOperationQueue *operationQueue = [NSOperationQueue new]; - [operationQueue setSuspended:YES]; - mappingOperationDataSource.operationQueue = operationQueue; - [managedObjectContext performBlockAndWait:^{ - RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; - mapper.mappingOperationDataSource = mappingOperationDataSource; - [mapper performMapping]; - }]; - - [operationQueue setSuspended:NO]; - [operationQueue waitUntilAllOperationsAreFinished]; - - [managedObjectContext performBlockAndWait:^{ - NSError *error; - NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Parent"]; - fetchRequest.predicate = [NSPredicate predicateWithFormat:@"parentID = %@", @1]; - fetchRequest.fetchLimit = 1; - NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; - RKParent *parent = [results lastObject]; - assertThat(parent, is(notNilValue())); - NSSet *children = [parent fatheredChildren]; - assertThat(children, hasCountOf(1)); - RKChild *child = [children anyObject]; - assertThat(child.father, is(notNilValue())); - }]; -} - -- (void)testMappingAPayloadContainingRepeatedObjectsDoesNotYieldDuplicatesWithFetchRequestMappingCache -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - managedObjectStore.managedObjectCache = managedObjectCache; - - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - childMapping.primaryKeyAttribute = @"childID"; - [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; - - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; - parentMapping.primaryKeyAttribute = @"parentID"; - [parentMapping mapRelationship:@"children" withMapping:childMapping]; - - RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; - // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary - // keys are not guaranteed to return in any particular order - [mappingProvider setObjectMapping:parentMapping forKeyPath:@"parents"]; - - NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"parents_and_children.json"]; - RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; - mapper.mappingOperationDataSource = mappingOperationDataSource; - [mapper performMapping]; - - NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Parent" predicate:nil error:nil]; - NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Child" predicate:nil error:nil]; - assertThatInteger(parentCount, is(equalToInteger(2))); - assertThatInteger(childrenCount, is(equalToInteger(4))); -} - -- (void)testMappingAPayloadContainingRepeatedObjectsDoesNotYieldDuplicatesWithInMemoryMappingCache -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKInMemoryManagedObjectCache *managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - managedObjectStore.managedObjectCache = managedObjectCache; - - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - childMapping.primaryKeyAttribute = @"childID"; - [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; - - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; - parentMapping.primaryKeyAttribute = @"parentID"; - [parentMapping mapRelationship:@"children" withMapping:childMapping]; - - RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; - // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary - // keys are not guaranteed to return in any particular order - [mappingProvider setObjectMapping:parentMapping forKeyPath:@"parents"]; - - NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"parents_and_children.json"]; - RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; - mapper.mappingOperationDataSource = mappingOperationDataSource; - [mapper performMapping]; - - NSError *error = nil; - BOOL success = [managedObjectStore.persistentStoreManagedObjectContextContext save:&error]; - assertThatBool(success, is(equalToBool(YES))); - NSLog(@"Failed to save MOC: %@", error); - assertThat(error, is(nilValue())); - - NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Parent"]; - NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForFetchRequest:fetchRequest error:&error]; - NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Child" predicate:nil error:nil]; - assertThatInteger(parentCount, is(equalToInteger(2))); - assertThatInteger(childrenCount, is(equalToInteger(4))); -} - -- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithFetchRequestMappingCache -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - managedObjectStore.managedObjectCache = managedObjectCache; - - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - childMapping.primaryKeyAttribute = @"childID"; - [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; - - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; - parentMapping.primaryKeyAttribute = @"parentID"; - [parentMapping mapRelationship:@"children" withMapping:childMapping]; - - RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; - // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary - // keys are not guaranteed to return in any particular order - [mappingProvider setObjectMapping:parentMapping forKeyPath:@"parents"]; - - NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"]; - RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; - mapper.mappingOperationDataSource = mappingOperationDataSource; - - RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelOff); - RKLogConfigureByName("RestKit/CoreData", RKLogLevelOff); - - [RKBenchmark report:@"Mapping with Fetch Request Cache" executionBlock:^{ - for (NSUInteger i = 0; i < 50; i++) { - [mapper performMapping]; - } - }]; - NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Parent" predicate:nil error:nil]; - NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Child" predicate:nil error:nil]; - assertThatInteger(parentCount, is(equalToInteger(25))); - assertThatInteger(childrenCount, is(equalToInteger(51))); -} - -- (void)testMappingAPayloadContainingRepeatedObjectsPerformsAcceptablyWithInMemoryMappingCache -{ - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - RKInMemoryManagedObjectCache *managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - managedObjectStore.managedObjectCache = managedObjectCache; - - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - childMapping.primaryKeyAttribute = @"childID"; - [childMapping addAttributeMappingsFromArray:@[@"name", @"childID"]]; - - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - [parentMapping addAttributeMappingsFromArray:@[@"parentID", @"name"]]; - parentMapping.primaryKeyAttribute = @"parentID"; - [parentMapping mapRelationship:@"children" withMapping:childMapping]; - - RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; - // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary - // keys are not guaranteed to return in any particular order - [mappingProvider setObjectMapping:parentMapping forKeyPath:@"parents"]; - - NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"benchmark_parents_and_children.json"]; - RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; - mapper.mappingOperationDataSource = mappingOperationDataSource; - RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelOff); - RKLogConfigureByName("RestKit/CoreData", RKLogLevelOff); - - [RKBenchmark report:@"Mapping with In Memory Cache" executionBlock:^{ - for (NSUInteger i = 0; i < 50; i++) { - [mapper performMapping]; - } - }]; - NSUInteger parentCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Parent" predicate:nil error:nil]; - NSUInteger childrenCount = [managedObjectStore.persistentStoreManagedObjectContextContext countForEntityForName:@"Child" predicate:nil error:nil]; - assertThatInteger(parentCount, is(equalToInteger(25))); - assertThatInteger(childrenCount, is(equalToInteger(51))); -} - -/* Test deprecated connectionMapping API */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -- (void)testShouldConnectRelationshipsByPrimaryKeyDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping* catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping* humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; - - // Create a cat to connect - RKCat* cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore save:nil]; - - NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman* human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError* error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, isNot(nilValue())); - assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); -} - -- (void)testConnectRelationshipsDoesNotLeakMemoryDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping* catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping* humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; - - // Create a cat to connect - RKCat* cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore save:nil]; - - NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman* human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - mappingOperationDataSource.operationQueue = [[NSOperationQueue new] autorelease]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError* error = nil; - [operation performMapping:&error]; - - assertThatInteger([operation retainCount], is(equalToInteger(1))); -} - -- (void)testConnectionOfHasManyRelationshipsByPrimaryKeyDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping* catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping* humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID"]; - - // Create a cat to connect - RKCat* cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore save:nil]; - - NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman* human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - NSError* error = nil; - operation.dataSource = mappingOperationDataSource; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, isNot(nilValue())); - assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); -} - -- (void)testShouldConnectRelationshipsByPrimaryKeyWithDifferentSourceAndDestinationKeyPathsDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping* catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping* humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID", @"catIDs"]]; - [humanMapping mapRelationship:@"cats" withMapping:catMapping]; - [humanMapping connectRelationship:@"cats" withObjectForPrimaryKeyAttribute:@"catIDs"]; - - // Create a couple of cats to connect - RKCat* asia = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - asia.name = @"Asia"; - asia.railsID = [NSNumber numberWithInt:31337]; - - RKCat* roy = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - roy.name = @"Reginald Royford Williams III"; - roy.railsID = [NSNumber numberWithInt:31338]; - - [managedObjectStore save:nil]; - - NSArray *catIDs = [NSArray arrayWithObjects:[NSNumber numberWithInt:31337], [NSNumber numberWithInt:31338], nil]; - NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"catIDs", catIDs, nil]; - RKHuman* human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError* error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.cats, isNot(nilValue())); - assertThat([human.cats valueForKeyPath:@"name"], containsInAnyOrder(@"Asia", @"Reginald Royford Williams III", nil)); -} - -- (void)testShouldDynamicallyConnectRelationshipsByPrimaryKeyWhenMatchingSucceedsDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping* catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping* humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID" whenValueOfKeyPath:@"name" isEqualTo:@"Blake"]; - - // Create a cat to connect - RKCat* cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore save:nil]; - - NSDictionary* mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman* human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError* error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, isNot(nilValue())); - assertThat(human.favoriteCat.name, is(equalTo(@"Asia"))); -} - -- (void)testShouldNotDynamicallyConnectRelationshipsByPrimaryKeyWhenMatchingFailsDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - - RKEntityMapping* catMapping = [RKEntityMapping mappingForEntityForName:@"Cat" inManagedObjectStore:managedObjectStore]; - catmapping.identificationAttributes = @[ @"railsID" ]; - [catMapping addAttributeMappingsFromArray:@[@"name"]]; - - RKEntityMapping* humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore]; - humanmapping.identificationAttributes = @[ @"railsID" ]; - [humanMapping addAttributeMappingsFromArray:@[@"name", @"favoriteCatID"]]; - [humanMapping hasOne:@"favoriteCat" withMapping:catMapping]; - [humanMapping connectRelationship:@"favoriteCat" withObjectForPrimaryKeyAttribute:@"favoriteCatID" whenValueOfKeyPath:@"name" isEqualTo:@"Jeff"]; - - // Create a cat to connect - RKCat* cat = [NSEntityDescription insertNewObjectForEntityForName:@"Cat" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - cat.name = @"Asia"; - cat.railsID = [NSNumber numberWithInt:31337]; - [managedObjectStore save:nil]; - - NSDictionary *mappableData = [NSDictionary dictionaryWithKeysAndObjects:@"name", @"Blake", @"favoriteCatID", [NSNumber numberWithInt:31337], nil]; - RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - RKMappingOperation* operation = [[RKMappingOperation alloc] initWithSourceObject:mappableData destinationObject:human mapping:humanMapping]; - operation.dataSource = mappingOperationDataSource; - NSError* error = nil; - BOOL success = [operation performMapping:&error]; - assertThatBool(success, is(equalToBool(YES))); - assertThat(human.favoriteCat, is(nilValue())); -} - -- (void)testShouldConnectRelationshipsByPrimaryKeyRegardlessOfOrderDeprecated { - RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; - NSManagedObjectContext *managedObjectContext = managedObjectStore.persistentStoreManagedObjectContextContext; - RKEntityMapping *parentMapping = [RKEntityMapping mappingForEntityForName:@"Parent" inManagedObjectStore:managedObjectStore]; - [parentMapping addAttributeMappingsFromArray:@[@"parentID"]]; - parentMapping.primaryKeyAttribute = @"parentID"; - - RKEntityMapping *childMapping = [RKEntityMapping mappingForEntityForName:@"Child" inManagedObjectStore:managedObjectStore]; - [childMapping addAttributeMappingsFromArray:@[@"fatherID"]]; - [childMapping mapRelationship:@"father" withMapping:parentMapping]; - [childMapping connectRelationship:@"father" withObjectForPrimaryKeyAttribute:@"fatherID"]; - - RKObjectMappingProvider *mappingProvider = [RKObjectMappingProvider new]; - // NOTE: This may be fragile. Reverse order seems to trigger them to be mapped parent first. NSDictionary - // keys are not guaranteed to return in any particular order - [mappingProvider setMapping:parentMapping forKeyPath:@"parents"]; - [mappingProvider setMapping:childMapping forKeyPath:@"children"]; - - NSDictionary *JSON = [RKTestFixture parsedObjectWithContentsOfFixture:@"ConnectingParents.json"]; - RKFetchRequestManagedObjectCache *managedObjectCache = [RKFetchRequestManagedObjectCache new]; - RKManagedObjectMappingOperationDataSource *mappingOperationDataSource = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContextContext - cache:managedObjectCache]; - - NSOperationQueue *operationQueue = [NSOperationQueue new]; - [operationQueue setSuspended:YES]; - mappingOperationDataSource.operationQueue = operationQueue; - - __block RKMappingResult *result; - [managedObjectContext performBlockAndWait:^{ - RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:JSON mappingsDictionary:mappingsDictionary]; - mapper.mappingOperationDataSource = mappingOperationDataSource; - result = [mapper performMapping]; - }]; - - [operationQueue setSuspended:NO]; - [operationQueue waitUntilAllOperationsAreFinished]; - - NSArray *children = [[result asDictionary] valueForKey:@"children"]; - assertThat(children, hasCountOf(1)); - RKChild *child = [children lastObject]; - assertThat(child.father, is(notNilValue())); -} - -#pragma GCC diagnostic pop - -@end diff --git a/Tests/Logic/CoreData/RKManagedObjectStoreTest.m b/Tests/Logic/CoreData/RKManagedObjectStoreTest.m index 360e75a4d5..703081d192 100644 --- a/Tests/Logic/CoreData/RKManagedObjectStoreTest.m +++ b/Tests/Logic/CoreData/RKManagedObjectStoreTest.m @@ -251,4 +251,24 @@ - (void)testThatAddingASQLiteStoreExcludesThePathFromiCloudBackups } #endif +- (void)testResetPersistentStoresDoesNotTriggerDeadlock +{ + RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; + NSManagedObject *managedObject1 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext]; + NSManagedObject *managedObject2 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; + NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; + [fetchRequest setSortDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES] ]]; + NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectStore.mainQueueManagedObjectContext sectionNameKeyPath:nil cacheName:nil]; + NSError *error = nil; + [fetchedResultsController performFetch:&error]; + BOOL success = [managedObjectStore resetPersistentStores:&error]; + expect(success).to.equal(YES); + expect(error).to.beNil(); + [fetchedResultsController performFetch:&error]; + [managedObject1 setValue:@"Blake" forKey:@"name"]; + [managedObject2.managedObjectContext performBlockAndWait:^{ + [managedObject2 setValue:@"Blake" forKey:@"name"]; + }]; +} + @end diff --git a/Tests/Logic/Network/RKObjectRequestOperationTest.m b/Tests/Logic/Network/RKObjectRequestOperationTest.m index b3e28f0ff2..99a951037a 100644 --- a/Tests/Logic/Network/RKObjectRequestOperationTest.m +++ b/Tests/Logic/Network/RKObjectRequestOperationTest.m @@ -463,6 +463,31 @@ - (void)testThatAnEmptyStringResponseBodyForAnObjectRequestOperationWithATargetO expect([requestOperation.mappingResult array]).to.contain(targetObject); } +- (void)testErrorReportingForPathPatternMismatch +{ + RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestComplexUser class]]; + [userMapping addAttributeMappingsFromArray:@[@"firstname"]]; + + NSURL *baseURL = [NSURL URLWithString:@"http://restkit.org/api/v1"]; + RKResponseDescriptor *responseDescriptor1 = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:@"/users/empty" keyPath:@"firstUser" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]; + responseDescriptor1.baseURL = baseURL; + RKResponseDescriptor *responseDescriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:@"/users/empty" keyPath:@"secondUser" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]; + responseDescriptor2.baseURL = baseURL; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"/users/empty" relativeToURL:[RKTestFactory baseURL]]]; + RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor1, responseDescriptor2 ]]; + NSOperationQueue *operationQueue = [NSOperationQueue new]; + [operationQueue addOperation:requestOperation]; + [operationQueue waitUntilAllOperationsAreFinished]; + expect(requestOperation.error).notTo.beNil(); + NSString *failureReason = [[requestOperation.error userInfo] valueForKey:NSLocalizedFailureReasonErrorKey]; + assertThat(failureReason, containsString(@"A 200 response was loaded from the URL 'http://127.0.0.1:4567/users/empty', which failed to match all (2) response descriptors:")); + assertThat(failureReason, containsString(@"failed to match: response URL 'http://127.0.0.1:4567/users/empty' is not relative to the baseURL 'http://restkit.org/api/v1'.")); + assertThat(failureReason, containsString(@"failed to match: response URL 'http://127.0.0.1:4567/users/empty' is not relative to the baseURL 'http://restkit.org/api/v1'.")); +} + +// Test trailing slash on the baseURL + #pragma mark - Block Tests - (void)testInvocationOfSuccessBlock @@ -501,7 +526,6 @@ - (void)testInvocationOfFailureBlock [operationQueue waitUntilAllOperationsAreFinished]; } - #pragma mark - Will Map Data Block - (void)testShouldAllowMutationOfTheParsedDataInWillMapData diff --git a/Tests/Logic/Network/RKResponseMapperOperationTest.m b/Tests/Logic/Network/RKResponseMapperOperationTest.m index 7d5e1be50c..ec66ecbf9c 100644 --- a/Tests/Logic/Network/RKResponseMapperOperationTest.m +++ b/Tests/Logic/Network/RKResponseMapperOperationTest.m @@ -200,4 +200,33 @@ - (void)testThatResponseMapperMatchesBaseURLWithPathAndQueryParametersAppropriat expect(mapper.responseMappingsDictionary).to.equal(expectedMappingsDictionary); } +- (void)testThatResponseDescriptorMismatchesIncludeHelpfulError +{ + NSURL *responseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/users"]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:responseURL statusCode:200 HTTPVersion:@"1.1" headerFields:@{@"Content-Type": @"application/json"}]; + NSData *data = [@"{\"some\": \"Data\"}" dataUsingEncoding:NSUTF8StringEncoding]; + NSURL *baseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/"]; + + RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + RKResponseDescriptor *responseDescriptor1 = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:@"/users" keyPath:@"this" statusCodes:[NSIndexSet indexSetWithIndex:200]]; + responseDescriptor1.baseURL = baseURL; + RKResponseDescriptor *responseDescriptor2 = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:@"/users" keyPath:@"that" statusCodes:[NSIndexSet indexSetWithIndex:200]]; + responseDescriptor2.baseURL = [NSURL URLWithString:@"http://google.com"]; + RKResponseDescriptor *responseDescriptor3 = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:@"users" keyPath:@"that" statusCodes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(202, 5)]]; + responseDescriptor3.baseURL = baseURL; + + RKObjectResponseMapperOperation *mapper = [[RKObjectResponseMapperOperation alloc] initWithResponse:response data:data responseDescriptors:@[ responseDescriptor1, responseDescriptor2, responseDescriptor3 ]]; + [mapper start]; + expect(mapper.error).notTo.beNil(); + expect([mapper.error code]).to.equal(RKMappingErrorNotFound); + NSString *failureReason = [[mapper.error userInfo] valueForKey:NSLocalizedFailureReasonErrorKey]; + assertThat(failureReason, containsString(@"A 200 response was loaded from the URL 'http://restkit.org/api/v1/users', which failed to match all (3) response descriptors")); + assertThat(failureReason, containsString(@"failed to match: response path 'users' did not match the path pattern '/users'.")); + assertThat(failureReason, containsString(@"failed to match: response URL 'http://restkit.org/api/v1/users' is not relative to the baseURL 'http://google.com'.")); + assertThat(failureReason, containsString(@"failed to match: response status code 200 is not within the range 202-206")); + + NSDictionary *expectedMappingsDictionary = @{}; + expect(mapper.responseMappingsDictionary).to.equal(expectedMappingsDictionary); +} + @end diff --git a/Tests/Logic/Network/RKRouterTest.m b/Tests/Logic/Network/RKRouterTest.m new file mode 100644 index 0000000000..c9d0e19274 --- /dev/null +++ b/Tests/Logic/Network/RKRouterTest.m @@ -0,0 +1,26 @@ +// +// RKRouterTest.m +// RestKit +// +// Created by Blake Watters on 12/7/12. +// Copyright (c) 2012 RestKit. All rights reserved. +// + +#import "RKTestEnvironment.h" +#import "RKRouter.h" + +@interface RKRouterTest : RKTestCase +@end + +@implementation RKRouterTest + +- (void)testChangingBaseURL +{ + NSURL *originalURL = [NSURL URLWithString:@"http://restkit.org/"]; + NSURL *newURL = [NSURL URLWithString:@"http://google.com/"]; + RKRouter *router = [[RKRouter alloc] initWithBaseURL:originalURL]; + router.baseURL = newURL; + expect(router.baseURL).to.equal(newURL); +} + +@end diff --git a/Tests/Logic/ObjectMapping/RKObjectManagerTest.m b/Tests/Logic/ObjectMapping/RKObjectManagerTest.m index 0b16d06faa..02ebd844cb 100644 --- a/Tests/Logic/ObjectMapping/RKObjectManagerTest.m +++ b/Tests/Logic/ObjectMapping/RKObjectManagerTest.m @@ -24,7 +24,9 @@ #import "RKEntityMapping.h" #import "RKHuman.h" #import "RKCat.h" +#import "RKTestUser.h" #import "RKObjectMapperTestModel.h" +#import "RKDynamicMapping.h" @interface RKSubclassedTestModel : RKObjectMapperTestModel @end @@ -526,6 +528,100 @@ - (void)testThatRegisteringARequestDescriptorForASubclassSecondWillMatchAppropri expect(dictionary).to.equal(@{ @"subclassed": @{ @"age": @(30) } }); } +- (void)testThatResponseDescriptorWithUnmanagedMappingTriggersCreationOfObjectRequestOperation +{ + RKObjectMapping *vanillaMapping = [RKObjectMapping requestMapping]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:vanillaMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithDynamicMappingContainingEntityMappingsTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + [dynamicMapping setObjectMapping:humanMapping whenValueOfKeyPath:@"whatever" isEqualTo:@"whatever"]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithDynamicMappingUsingABlockTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKDynamicMapping *dynamicMapping = [RKDynamicMapping new]; + [dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) { + return humanMapping; + }]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithUnmanagedMappingContainingRelationshipMappingWithEntityMappingsTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [objectMapping addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:humanMapping]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objectMapping pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testThatResponseDescriptorWithUnmanagedMappingContainingRelationshipMappingWithEntityMappingsDeepWithinObjectGraphTriggersCreationOfManagedObjectRequestOperation +{ + RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:_objectManager.managedObjectStore]; + RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [objectMapping addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:humanMapping]; + RKObjectMapping *objectMapping2 = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [objectMapping2 addRelationshipMappingWithSourceKeyPath:@"relationship" mapping:objectMapping]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:objectMapping2 pathPattern:nil keyPath:nil statusCodes:nil]; + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.managedObjectStore = [RKTestFactory managedObjectStore]; + [manager addResponseDescriptor:responseDescriptor]; + RKObjectRequestOperation *objectRequestOperation = [manager appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:@"/something" parameters:nil]; + expect(objectRequestOperation).to.beInstanceOf([RKManagedObjectRequestOperation class]); +} + +- (void)testChangingHTTPClient +{ + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]]; + manager.HTTPClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:@"http://google.com/"]]; + expect([manager.baseURL absoluteString]).to.equal(@"http://google.com/"); +} + +- (void)testPostingOneObjectAndGettingResponseMatchingMultipleDescriptors +{ + RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[RKTestFactory baseURL]]; + RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; + [userMapping addAttributeMappingsFromDictionary:@{ @"fullname": @"name" }]; + RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:nil keyPath:@"data.STUser" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]; + RKObjectMapping *metaMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; + [metaMapping addAttributeMappingsFromArray:@[ @"status", @"version" ]]; + RKResponseDescriptor *metaResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:metaMapping pathPattern:nil keyPath:@"meta" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]; + + [manager addResponseDescriptorsFromArray:@[ userResponseDescriptor, metaResponseDescriptor ]]; + RKTestUser *user = [RKTestUser new]; + RKObjectRequestOperation *requestOperation = [manager appropriateObjectRequestOperationWithObject:user method:RKRequestMethodPOST path:@"/ComplexUser" parameters:nil]; + [requestOperation start]; + [requestOperation waitUntilFinished]; + + expect(requestOperation.mappingResult).notTo.beNil(); + expect([requestOperation.mappingResult array]).to.haveCountOf(2); +} + //- (void)testShouldHandleConnectionFailures //{ // NSString *localBaseURL = [NSString stringWithFormat:@"http://127.0.0.1:3001"]; diff --git a/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m b/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m index 37664773ac..2f11a6b6b3 100644 --- a/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m +++ b/Tests/Logic/ObjectMapping/RKObjectMappingNextGenTest.m @@ -2063,4 +2063,23 @@ - (void)testUpdatingArrayOfExistingCats assertThat([humans objectAtIndex:1], is(equalTo(human2))); } +- (void)testMappingMultipleKeyPathsAtRootOfObject +{ + RKObjectMapping *mapping1 = [RKObjectMapping mappingForClass:[RKTestUser class]]; + [mapping1 addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"prodId" toKeyPath:@"userID"]]; + + RKObjectMapping *mapping2 = [RKObjectMapping mappingForClass:[RKTestUser class]]; + [mapping2 addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"catId" toKeyPath:@"userID"]]; + + NSDictionary *dictionary = [RKTestFixture parsedObjectWithContentsOfFixture:@"SameKeyDifferentTargetClasses.json"]; + RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithObject:dictionary mappingsDictionary:@{ @"products": mapping1, @"categories": mapping2 }]; + RKObjectMappingOperationDataSource *dataSource = [RKObjectMappingOperationDataSource new]; + mapper.mappingOperationDataSource = dataSource; + [mapper start]; + + expect(mapper.error).to.beNil(); + expect(mapper.mappingResult).notTo.beNil(); + expect([mapper.mappingResult array]).to.haveCountOf(4); +} + @end diff --git a/Tests/Logic/ObjectMapping/RKPaginatorTest.m b/Tests/Logic/ObjectMapping/RKPaginatorTest.m index 6d0b8e3a87..87fe2df8e4 100644 --- a/Tests/Logic/ObjectMapping/RKPaginatorTest.m +++ b/Tests/Logic/ObjectMapping/RKPaginatorTest.m @@ -350,4 +350,24 @@ - (void)testKnowledgeOfPreviousPage assertThatBool([mockPaginator hasPreviousPage], is(equalToBool(NO))); } +- (void)testProxyAttributes +{ + RKPaginator *paginator = [RKPaginator new]; + [paginator setValue:@(12345) forKey:@"pageCountNumber"]; + expect(paginator.pageCount).to.equal(12345); + expect([paginator valueForKey:@"pageCountNumber"]).to.equal(12345); + + [paginator setValue:@(1) forKey:@"currentPageNumber"]; + expect(paginator.currentPage).to.equal(1); + expect([paginator valueForKey:@"currentPageNumber"]).to.equal(1); + + [paginator setValue:@(25) forKey:@"objectCountNumber"]; + expect(paginator.objectCount).to.equal(25); + expect([paginator valueForKey:@"objectCountNumber"]).to.equal(25); + + [paginator setValue:@(10) forKey:@"perPageNumber"]; + expect(paginator.perPage).to.equal(10); + expect([paginator valueForKey:@"perPageNumber"]).to.equal(10); +} + @end diff --git a/Tests/Server/server.rb b/Tests/Server/server.rb index eb1bc3b06e..156e2d5dd5 100755 --- a/Tests/Server/server.rb +++ b/Tests/Server/server.rb @@ -283,6 +283,11 @@ def render_fixture(path, options = {}) status 200 ' ' end + + post '/ComplexUser' do + content_type 'application/json' + render_fixture('/JSON/ComplexNestedUser.json', :status => 200) + end # start the server if ruby file executed directly run! if app_file == $0 diff --git a/VERSION b/VERSION index e7ee464d27..e2f6c29a83 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.20.0-pre2 \ No newline at end of file +0.20.0-pre3 \ No newline at end of file