diff --git a/Code/RKManagedModel.m b/Code/RKManagedModel.m index 3817efdb..ef734614 100644 --- a/Code/RKManagedModel.m +++ b/Code/RKManagedModel.m @@ -101,9 +101,25 @@ return @"id"; } +/** + * TODO: Unwind assumptions about the primaryKey + * + * Right now we make the blanket assumption that Primary Keys are stored as NSNumber values. We + * cast from NSStrings into NSNumbers to fix a weird bug Jeremy encountered with the subtle predicate + * differences causes nil return values in some cases. This needs to be better understood and the assumptions + * unwound. + */ + (id)findByPrimaryKey:(id)value { - NSPredicate* predicate = [NSPredicate predicateWithFormat:@"%K = %@", [self primaryKey], value]; - return [self objectWithPredicate:predicate]; + id primaryKeyValue = nil; + if ([value isKindOfClass:[NSString class]]) { + // Cast from string to a number + primaryKeyValue = [NSNumber numberWithInt:[(NSString*)value integerValue]]; + } else { + // Make blind assumption here. + primaryKeyValue = value; + } + NSPredicate* predicate = [NSPredicate predicateWithFormat:@"%K = %@", [self primaryKey], primaryKeyValue]; + return [self objectWithPredicate:predicate]; } + (NSDictionary*)elementToPropertyMappings { diff --git a/Code/RKModelLoader.m b/Code/RKModelLoader.m index 2553cc54..e1d3c234 100644 --- a/Code/RKModelLoader.m +++ b/Code/RKModelLoader.m @@ -30,7 +30,7 @@ } - (BOOL)processResponse:(RKResponse*)response { - NSString* errorMessage; + NSString* errorMessage = nil; RKRequest* request = response.request; if ([response isFailure]) { [_delegate modelLoaderRequest:response.request didFailWithError:response.failureError response:response model:(id)request.userData]; diff --git a/Code/RKModelMapper.m b/Code/RKModelMapper.m index 514f4a37..1eec3bdf 100644 --- a/Code/RKModelMapper.m +++ b/Code/RKModelMapper.m @@ -86,7 +86,7 @@ // Don't set the property, both are nil } else if (nil == propertyValue || [propertyValue isKindOfClass:[NSNull class]]) { // Clear out the value to reset it - [model setNilValueForKey:propertyName]; + [model setValue:nil forKey:propertyName]; } else if (currentValue == nil || [currentValue isKindOfClass:[NSNull class]]) { // Existing value was nil, just set the property and be happy [model setValue:propertyValue forKey:propertyName]; diff --git a/Code/RKResponse.m b/Code/RKResponse.m index 2a4551c3..3a450be5 100644 --- a/Code/RKResponse.m +++ b/Code/RKResponse.m @@ -1,6 +1,6 @@ // // RKResponse.m -// RestKit +// RKFramework // // Created by Blake Watters on 7/28/09. // Copyright 2009 Two Toasters. All rights reserved. @@ -18,6 +18,7 @@ if (self = [super init]) { _payload = [[NSMutableData alloc] init]; _failureError = nil; + _loading = NO; } return self; @@ -53,7 +54,14 @@ } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - [_payload appendData:data]; + if (NO == _loading) { + _loading = YES; + if ([[_request delegate] respondsToSelector:@selector(requestDidStartLoad:)]) { + [[_request delegate] requestDidStartLoad:_request]; + } + } + + [_payload appendData:data]; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { @@ -62,15 +70,24 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [connection release]; - NSDate* receivedAt = [NSDate date]; + NSDate* receivedAt = [NSDate date]; // TODO - Carry around this timestamp on the response or request? NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[_request HTTPMethod], @"HTTPMethod", [_request URL], @"URL", receivedAt, @"receivedAt", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification object:self userInfo:userInfo]; - [[_request delegate] performSelector:[_request callback] withObject:self]; + [[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification object:self userInfo:userInfo]; + + [[_request delegate] performSelector:[_request callback] withObject:self]; + + if ([[_request delegate] respondsToSelector:@selector(requestDidFinishLoad:)]) { + [[_request delegate] requestDidFinishLoad:_request]; + } } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { _failureError = [error retain]; [[_request delegate] performSelector:[_request callback] withObject:self]; + + if ([[_request delegate] respondsToSelector:@selector(request:didFailLoadWithError:)]) { + [[_request delegate] request:_request didFailLoadWithError:error]; + } } - (NSString*)localizedStatusCodeString { diff --git a/Code/RestKit/RKRequest.h b/Code/RestKit/RKRequest.h index 8f32152a..038213bf 100644 --- a/Code/RestKit/RKRequest.h +++ b/Code/RestKit/RKRequest.h @@ -100,3 +100,16 @@ - (void)delete; @end + +/** + * Lifecycle events for RKRequests + * + * Modeled off of TTURLRequest + */ +@protocol RKRequestDelegate +@optional +- (void)requestDidStartLoad:(RKRequest*)request; +- (void)requestDidFinishLoad:(RKRequest*)request; +- (void)request:(RKRequest*)request didFailLoadWithError:(NSError*)error; +- (void)requestDidCancelLoad:(RKRequest*)request; // not yet implemented +@end diff --git a/Code/RestKit/RKResponse.h b/Code/RestKit/RKResponse.h index 16afa1b7..b5b024ff 100644 --- a/Code/RestKit/RKResponse.h +++ b/Code/RestKit/RKResponse.h @@ -15,6 +15,7 @@ NSHTTPURLResponse* _httpURLResponse; NSMutableData* _payload; NSError* _failureError; + BOOL _loading; } /** diff --git a/RestKit.xcodeproj/project.pbxproj b/RestKit.xcodeproj/project.pbxproj index 0bd3d69a..110d61b7 100644 --- a/RestKit.xcodeproj/project.pbxproj +++ b/RestKit.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 256FD643112C7A5C0077F340 /* libjson.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 256FD631112C79750077F340 /* libjson.a */; }; 256FD651112C7B780077F340 /* RKMappableObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 256FD64F112C7B780077F340 /* RKMappableObject.m */; }; 256FD652112C7B780077F340 /* RKMappableAssociation.m in Sources */ = {isa = PBXBuildFile; fileRef = 256FD650112C7B780077F340 /* RKMappableAssociation.m */; }; + 256FDE55112DB0B90077F340 /* RKModelMapperSpecModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 256FDE54112DB0B90077F340 /* RKModelMapperSpecModel.m */; }; 2580B068102E0F1000832D07 /* RKModelLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 2580B066102E0F1000832D07 /* RKModelLoader.h */; }; 2580B069102E0F1000832D07 /* RKModelLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 2580B067102E0F1000832D07 /* RKModelLoader.m */; }; 2580B0C7102E1EBC00832D07 /* libElementParser.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F4E18DB102DD31E00320118 /* libElementParser.a */; }; @@ -67,7 +68,7 @@ 3F4E18F9102DD38800320118 /* RKParamsDataAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E18EA102DD38700320118 /* RKParamsDataAttachment.m */; }; 3F4E18FA102DD38800320118 /* RKParamsFileAttachment.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4E18EB102DD38700320118 /* RKParamsFileAttachment.h */; }; 3F4E18FB102DD38800320118 /* RKParamsFileAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E18EC102DD38700320118 /* RKParamsFileAttachment.m */; }; - 3F4E18FC102DD38800320118 /* RKRequest.m in Headers */ = {isa = PBXBuildFile; fileRef = 3F4E18ED102DD38700320118 /* RKRequest.m */; }; + 3F4E18FC102DD38800320118 /* RKRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4E18ED102DD38700320118 /* RKRequest.h */; }; 3F4E18FD102DD38800320118 /* RKRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F4E18EE102DD38700320118 /* RKRequest.m */; }; 3F4E18FE102DD38800320118 /* RKRequestSerializable.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4E18EF102DD38700320118 /* RKRequestSerializable.h */; }; 3F4E18FF102DD38800320118 /* RKResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F4E18F0102DD38700320118 /* RKResponse.h */; }; @@ -225,6 +226,8 @@ 256FD64D112C7AF50077F340 /* RKMappableAssociation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKMappableAssociation.h; sourceTree = ""; }; 256FD64F112C7B780077F340 /* RKMappableObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKMappableObject.m; sourceTree = ""; }; 256FD650112C7B780077F340 /* RKMappableAssociation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKMappableAssociation.m; sourceTree = ""; }; + 256FDE53112DB0B90077F340 /* RKModelMapperSpecModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKModelMapperSpecModel.h; sourceTree = ""; }; + 256FDE54112DB0B90077F340 /* RKModelMapperSpecModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKModelMapperSpecModel.m; sourceTree = ""; }; 2580B066102E0F1000832D07 /* RKModelLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKModelLoader.h; path = RestKit/RKModelLoader.h; sourceTree = ""; }; 2580B067102E0F1000832D07 /* RKModelLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKModelLoader.m; sourceTree = ""; }; 25FCDDD91035BC85005418A7 /* RKManagedModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKManagedModel.h; path = RestKit/RKManagedModel.h; sourceTree = ""; }; @@ -250,7 +253,7 @@ 3F4E18EA102DD38700320118 /* RKParamsDataAttachment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKParamsDataAttachment.m; sourceTree = ""; }; 3F4E18EB102DD38700320118 /* RKParamsFileAttachment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKParamsFileAttachment.h; path = RestKit/RKParamsFileAttachment.h; sourceTree = ""; }; 3F4E18EC102DD38700320118 /* RKParamsFileAttachment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKParamsFileAttachment.m; sourceTree = ""; }; - 3F4E18ED102DD38700320118 /* RKRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRequest.m; sourceTree = ""; }; + 3F4E18ED102DD38700320118 /* RKRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKRequest.h; path = RestKit/RKRequest.h; sourceTree = ""; }; 3F4E18EE102DD38700320118 /* RKRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRequest.m; sourceTree = ""; }; 3F4E18EF102DD38700320118 /* RKRequestSerializable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKRequestSerializable.h; path = RestKit/RKRequestSerializable.h; sourceTree = ""; }; 3F4E18F0102DD38700320118 /* RKResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKResponse.h; path = RestKit/RKResponse.h; sourceTree = ""; }; @@ -352,6 +355,8 @@ 3F032AA710FFBBCD00F35142 /* RKHouse.m */, 3F032AA910FFBC1F00F35142 /* RKResident.h */, 3F032AAA10FFBC1F00F35142 /* RKResident.m */, + 256FDE53112DB0B90077F340 /* RKModelMapperSpecModel.h */, + 256FDE54112DB0B90077F340 /* RKModelMapperSpecModel.m */, ); path = Models; sourceTree = ""; @@ -422,7 +427,7 @@ children = ( 3F4E18E3102DD38700320118 /* RKClient.h */, 3F4E18E4102DD38700320118 /* RKClient.m */, - 3F4E18ED102DD38700320118 /* RKRequest.m */, + 3F4E18ED102DD38700320118 /* RKRequest.h */, 3F4E18EE102DD38700320118 /* RKRequest.m */, 3F4E18F0102DD38700320118 /* RKResponse.h */, 3F4E18F1102DD38800320118 /* RKResponse.m */, @@ -517,7 +522,7 @@ 3F4E18F6102DD38800320118 /* RKParamsAttachment.h in Headers */, 3F4E18F8102DD38800320118 /* RKParamsDataAttachment.h in Headers */, 3F4E18FA102DD38800320118 /* RKParamsFileAttachment.h in Headers */, - 3F4E18FC102DD38800320118 /* RKRequest.m in Headers */, + 3F4E18FC102DD38800320118 /* RKRequest.h in Headers */, 3F4E18FE102DD38800320118 /* RKRequestSerializable.h in Headers */, 3F4E18FF102DD38800320118 /* RKResponse.h in Headers */, 3F4E191A102DD42F00320118 /* NSDictionary+RKRequestSerialization.h in Headers */, @@ -737,6 +742,7 @@ 256FD523112C6A340077F340 /* Data Model.xcdatamodel in Sources */, 256FD651112C7B780077F340 /* RKMappableObject.m in Sources */, 256FD652112C7B780077F340 /* RKMappableAssociation.m in Sources */, + 256FDE55112DB0B90077F340 /* RKModelMapperSpecModel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Specs/Models/RKModelMapperSpecModel.h b/Specs/Models/RKModelMapperSpecModel.h new file mode 100644 index 00000000..6c9ecd41 --- /dev/null +++ b/Specs/Models/RKModelMapperSpecModel.h @@ -0,0 +1,23 @@ +// +// RKModelMapperSpecModel.h +// RestKit +// +// Created by Blake Watters on 2/18/10. +// Copyright 2010 Two Toasters. All rights reserved. +// + +#import + + +@interface RKModelMapperSpecModel : NSObject { + NSString* _name; + NSNumber* _age; + NSDate* _createdAt; +} + +@property (nonatomic,retain) NSString* name; +@property (nonatomic,retain) NSNumber* age; +@property (nonatomic,retain) NSDate* createdAt; + +@end + diff --git a/Specs/Models/RKModelMapperSpecModel.m b/Specs/Models/RKModelMapperSpecModel.m new file mode 100644 index 00000000..794955d4 --- /dev/null +++ b/Specs/Models/RKModelMapperSpecModel.m @@ -0,0 +1,18 @@ +// +// RKModelMapperSpecModel.m +// RestKit +// +// Created by Blake Watters on 2/18/10. +// Copyright 2010 Two Toasters. All rights reserved. +// + +#import "RKModelMapperSpecModel.h" + + +@implementation RKModelMapperSpecModel + +@synthesize name = _name; +@synthesize age = _age; +@synthesize createdAt = _createdAt; + +@end diff --git a/Specs/RKModelMapperSpec.m b/Specs/RKModelMapperSpec.m index fa53ed4d..2d44acc7 100644 --- a/Specs/RKModelMapperSpec.m +++ b/Specs/RKModelMapperSpec.m @@ -128,6 +128,58 @@ [expectThat([[result hasMany] count]) should:be(2)]; } +- (void)itShouldNotUpdateNilPropertyToNil { + RKModelMapperSpecModel* model = [[RKModelMapperSpecModel alloc] autorelease]; + RKModelMapper* mapper = [[RKModelMapper alloc] init]; + [mapper updateObject:model ifNewPropertyPropertyValue:nil forPropertyNamed:@"name"]; + + [expectThat(model.name) should:be(nil)]; +} + +- (void)itShouldBeAbleToSetNonNilPropertiesToNil { + RKModelMapperSpecModel* model = [[RKModelMapperSpecModel alloc] autorelease]; + model.age = [NSNumber numberWithInt:0]; + RKModelMapper* mapper = [[RKModelMapper alloc] init]; + [mapper updateObject:model ifNewPropertyPropertyValue:nil forPropertyNamed:@"age"]; + + [expectThat(model.age) should:be(nil)]; +} + +- (void)itShouldBeAbleToSetNilPropertiesToNonNil { + RKModelMapperSpecModel* model = [[OTRestModelMapperTestModel alloc] autorelease]; + RKModelMapper* mapper = [[RKModelMapper alloc] init]; + [mapper updateObject:model ifNewPropertyPropertyValue:[NSNumber numberWithInt:0] forPropertyNamed:@"age"]; + + [expectThat(model.age) should:be([NSNumber numberWithInt:0])]; +} + +- (void)itShouldBeAbleToSetNonNilNSStringPropertiesToNonNil { + RKModelMapperSpecModel* model = [[RKModelMapperSpecModel alloc] autorelease]; + RKModelMapper* mapper = [[RKModelMapper alloc] init]; + + model.name = @"Bob"; + [mapper updateObject:model ifNewPropertyPropertyValue:@"Will" forPropertyNamed:@"name"]; + [expectThat(model.name) should:be(@"Will")]; +} + +- (void)itShouldBeAbleToSetNonNilNSNumberPropertiesToNonNil { + RKModelMapperSpecModel* model = [[RKModelMapperSpecModel alloc] autorelease]; + RKModelMapper* mapper = [[RKModelMapper alloc] init]; + + model.age = [NSNumber numberWithInt:16]; + [mapper updateObject:model ifNewPropertyPropertyValue:[NSNumber numberWithInt:17] forPropertyNamed:@"age"]; + [expectThat(model.age) should:be([NSNumber numberWithInt:17])]; +} + +- (void)itShouldBeAbleToSetNonNilNSDatePropertiesToNonNil { + RKModelMapperSpecModel* model = [[RKModelMapperSpecModel alloc] autorelease]; + RKModelMapper* mapper = [[RKModelMapper alloc] init]; + + model.createdAt = [NSDate date]; + [mapper updateObject:model ifNewPropertyPropertyValue:[NSDate dateWithTimeIntervalSince1970:0] forPropertyNamed:@"createdAt"]; + [expectThat(model.createdAt) should:be([NSDate dateWithTimeIntervalSince1970:0])]; +} + @end @implementation RKModelMapperSpec (Private) diff --git a/Specs/main.m b/Specs/main.m index 66c5e4ff..a1a5b61b 100644 --- a/Specs/main.m +++ b/Specs/main.m @@ -7,7 +7,7 @@ // #import -#import "UISpec.h" +#import int main(int argc, char *argv[]) {