diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m index e93a32cb..63f06eae 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m @@ -27,6 +27,7 @@ // Put current user id first because it might be nil. [[DBUser currentUser].userID isEqualToNumber:_requiredUserID]) { isAuthenticated = YES; + // TODO: Move this isAuthenticated logic into the model! } else if (_requiredUserID == nil && [DBUser currentUser].singleAccessToken && [DBUser currentUser] != nil) { diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m index bb992433..0c817a28 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m @@ -98,6 +98,7 @@ [self invalidateModel]; } +// TODO: Move login and sign-up into the model as higher level concepts - (void)loginOrSignup { if (_showingSignup) { // Signup @@ -142,6 +143,7 @@ assert([objects count] == 1); DBUser* user = [objects objectAtIndex:0]; NSLog(@"Authentication Token: %@", user.singleAccessToken); + // TODO: Move this into the DBUser... [[NSUserDefaults standardUserDefaults] setObject:user.userID forKey:kCurrentUserIDKey]; [[NSUserDefaults standardUserDefaults] synchronize]; [[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedInNotificationName object:user]; @@ -149,7 +151,12 @@ } - (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error { - [[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; + // TODO: TTAlert??? + [[[[UIAlertView alloc] initWithTitle:@"Error" + message:[error localizedDescription] + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil] autorelease] show]; } @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostTableViewController.m index 00b81c50..f9bdb103 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostTableViewController.m @@ -16,19 +16,23 @@ if (self = [super initWithStyle:UITableViewStyleGrouped]) { _post = [[DBPost objectWithPrimaryKeyValue:postID] retain]; } + return self; } - (id)initWithTopicID:(NSString*)topicID { if (self = [super initWithStyle:UITableViewStyleGrouped]) { + // TODO: Why are we using topic ID here? _topicID = [[NSNumber numberWithInt:[topicID intValue]] retain]; } + return self; } - (void)dealloc { - [_post release]; - [_topicID release]; + TT_RELEASE_SAFELY(_post); + TT_RELEASE_SAFELY(_topicID); + [super dealloc]; } @@ -46,7 +50,10 @@ self.tableViewStyle = UITableViewStyleGrouped; self.autoresizesForKeyboard = YES; self.variableHeightRows = YES; + [[TTNavigator navigator].URLMap from:@"db://updateAttachment" toObject:self selector:@selector(updateAttachment)]; + + // TODO: Use self.post method? if (nil == _post) { _post = [[DBPost object] retain]; _post.topicID = _topicID; @@ -64,6 +71,7 @@ } - (void)createModel { + // TODO: Move this into the model: [self.currentUser canModifyObject:_post] BOOL isAuthorizedUser = [[DBUser currentUser].userID isEqualToNumber:_post.userID] || [self isNewRecord]; NSMutableArray* items = [NSMutableArray array]; @@ -75,6 +83,7 @@ // has new attachment. show it. allow update. [items addObject:[TTTableImageItem itemWithText:@"Tap to Replace Image" imageURL:@"" defaultImage:_newAttachment URL:@"db://updateAttachment"]]; } else if (![[_post attachmentPath] isWhitespaceAndNewlines]) { + // TODO: Create model method to determine if there is an attachment // Has existing attachment. allow replace NSString* url = _post.attachmentPath; [items addObject:[TTTableImageItem itemWithText:@"Tap to Replace Image" imageURL:url defaultImage:nil URL:@"db://updateAttachment"]]; @@ -115,15 +124,7 @@ [self presentModalViewController:controller animated:YES]; } -- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - _newAttachment = [info objectForKey:UIImagePickerControllerOriginalImage]; - [self dismissModalViewControllerAnimated:YES]; - [self invalidateModel]; -} - -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [self dismissModalViewControllerAnimated:YES]; -} +#pragma mark Actions - (void)createButtonWasPressed:(id)sender { _post.body = _bodyTextEditor.text; @@ -131,7 +132,6 @@ [[RKObjectManager sharedManager] postObject:_post delegate:self]; } - - (void)updateButtonWasPressed:(id)sender { _post.body = _bodyTextEditor.text; _post.newAttachment = _newAttachment; @@ -142,10 +142,26 @@ [[RKObjectManager sharedManager] deleteObject:_post delegate:self]; } +#pragma mark UIImagePickerControllerDelegate methods + +- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { + _newAttachment = [info objectForKey:UIImagePickerControllerOriginalImage]; + [self dismissModalViewControllerAnimated:YES]; + [self invalidateModel]; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + [self dismissModalViewControllerAnimated:YES]; +} + +#pragma mark RKObjectLoaderDelegate methods + - (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects { + // TODO: RKLog helpers? NSLog(@"Loaded Objects: %@", objects); NSLog(@"Status Code: %d", objectLoader.response.statusCode); // Post notification telling view controllers to reload. + // TODO [[NSNotificationCenter defaultCenter] postNotificationName:kObjectCreatedUpdatedOrDestroyedNotificationName object:objects]; // dismiss. [self.navigationController popViewControllerAnimated:YES]; @@ -155,12 +171,15 @@ [[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; } -// Text Editor Delegate +#pragma mark TTTextEditorDelegate methods - (void)textEditorDidBeginEditing:(TTTextEditor*)textEditor { - UIBarButtonItem* doneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:textEditor action:@selector(resignFirstResponder)]; - [doneButton autorelease]; + UIBarButtonItem* doneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" + style:UIBarButtonItemStyleDone + target:textEditor + action:@selector(resignFirstResponder)]; self.navigationItem.rightBarButtonItem = doneButton; + [doneButton release]; } - (void)textEditorDidEndEditing:(TTTextEditor*)textEditor { diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.h index 890ed4b8..b95d34b8 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.h @@ -6,15 +6,21 @@ // Copyright 2011 Two Toasters. All rights reserved. // -#import #import #import "DBResourceListTableViewController.h" #import "DBTopic.h" +/** + * Displays a table of Posts within a given Topic + */ @interface DBPostsTableViewController : DBResourceListTableViewController { + // TODO: Just use a Topic NSString* _topicID; } +/** + * The Topic we are viewing Posts within + */ @property (nonatomic, readonly) DBTopic* topic; @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m index 2312eaca..15116a18 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m @@ -17,6 +17,7 @@ if (self = [super initWithStyle:UITableViewStylePlain]) { _topicID = [topicID retain]; self.title = @"Posts"; + // TODO: Use routing or something to generate this URL. RKGeneratePathWithObject _resourcePath = [[NSString stringWithFormat:@"/topics/%@/posts", _topicID] retain]; _resourceClass = [DBPost class]; } @@ -30,28 +31,37 @@ - (void)loadView { [super loadView]; + self.variableHeightRows = YES; - UIBarButtonItem* newItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonWasPressed:)] autorelease]; + UIBarButtonItem* newItem = [[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemAdd + target:self + action:@selector(addButtonWasPressed:)] autorelease]; self.navigationItem.rightBarButtonItem = newItem; } - (void)addButtonWasPressed:(id)sender { + // TODO: Move this onto the model... + // RKMakeObjectPath / RKGeneratePathWithObject / RKGeneratePathForObject(@"db://topics/(topicID)/posts/new", self.topic); NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts/new", _topicID]; TTOpenURL(url); } - (void)createModel { if (nil == [DBTopic objectWithPrimaryKeyValue:_topicID]) { + // TODO: Cleanup??? // this topic was deleted or something. [self.navigationController popToRootViewControllerAnimated:YES]; return; } + [super createModel]; } - (void)didLoadModel:(BOOL)firstTime { [super didLoadModel:firstTime]; + if ([self.model isKindOfClass:[RKRequestTTModel class]]) { RKRequestTTModel* model = (RKRequestTTModel*)self.model; NSMutableArray* postItems = [NSMutableArray arrayWithCapacity:[model.objects count]]; @@ -68,7 +78,7 @@ [topicItems addObject:[TTTableTextItem itemWithText:@"Edit" URL:editURL]]; } - for(DBPost* post in model.objects) { + for (DBPost* post in model.objects) { NSString* url = [NSString stringWithFormat:@"db://posts/%@", post.postID]; NSString* imageURL = post.attachmentPath; TTTableImageItem* item = [TTTableImageItem itemWithText:post.body diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.h index 243ffda0..29416d6e 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.h @@ -6,7 +6,6 @@ // Copyright 2011 Two Toasters. All rights reserved. // -#import #import #import diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m index 76964720..fa9f1023 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m @@ -10,10 +10,12 @@ #import "DBManagedObjectCache.h" @implementation UINavigationBar (CustomImage) + - (void)drawRect:(CGRect)rect { UIImage *image = [UIImage imageNamed:@"navigationBarBackground.png"]; [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; } + @end @implementation DBResourceListTableViewController @@ -45,6 +47,7 @@ UIView* tableSpacer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 31)]; self.tableView.tableHeaderView = tableSpacer; + // TODO: Use more generic notifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userStateChanged:) name:kUserLoggedInNotificationName object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userStateChanged:) name:kUserLoggedOutNotificationName object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadNotification:) name:kObjectCreatedUpdatedOrDestroyedNotificationName object:nil]; @@ -56,12 +59,12 @@ - (void)viewDidUnload { [super viewDidUnload]; - [_loadedAtLabel release]; - _loadedAtLabel = nil; + TT_RELEASE_SAFELY(_loadedAtLabel); [[NSNotificationCenter defaultCenter] removeObserver:self]; } +// TODO: Why not just use invalidateModel as the target of the notifications? - (void)reloadNotification:(NSNotification*)note { [self invalidateModel]; } @@ -70,12 +73,15 @@ [self invalidateModel]; } +// TODO: Why not use a createResourcePath method instead of an ivar? - (void)createModel { self.model = [[[RKRequestTTModel alloc] initWithResourcePath:_resourcePath] autorelease]; } +// TODO: Comment me up! - (void)didLoadModel:(BOOL)firstTime { [super didLoadModel:firstTime]; + if ([self.model isKindOfClass:[RKRequestTTModel class]]) { RKRequestTTModel* model = (RKRequestTTModel*)self.model; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicViewController.m index 4ef99320..d38360ff 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicViewController.m @@ -20,10 +20,11 @@ } - (void)dealloc { - [_topic release]; + TT_RELEASE_SAFELY(_topic); [super dealloc]; } +// TODO: Move this into the model... - (BOOL)isNewRecord { return [[_topic topicID] intValue] == 0; } @@ -67,6 +68,8 @@ self.dataSource = [TTListDataSource dataSourceWithItems:items]; } +#pragma mark Actions + - (void)createButtonWasPressed:(id)sender { _topic.name = _topicNameField.text; [[RKObjectManager sharedManager] postObject:_topic delegate:self]; @@ -81,16 +84,20 @@ [[RKObjectManager sharedManager] deleteObject:_topic delegate:self]; } +#pragma mark RKObjectLoaderDelegate methods + - (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects { NSLog(@"Loaded Objects: %@", objects); NSLog(@"Status Code: %d", objectLoader.response.statusCode); // Post notification telling view controllers to reload. + // TODO: Generalize this notification [[NSNotificationCenter defaultCenter] postNotificationName:kObjectCreatedUpdatedOrDestroyedNotificationName object:objects]; // dismiss. [self.navigationController popViewControllerAnimated:YES]; } - (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error { + // TODO: TTAlert or create a DBErrorAlert helper? [[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m index f6bcbd85..12c6d9b5 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m @@ -16,7 +16,7 @@ - (id)initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query { if (self = [super initWithNavigatorURL:URL query:query]) { self.title = @"Topics"; - _tableTitleHeaderLabel.text = @"Recent Topics"; + _tableTitleHeaderLabel.text = @"Recent Topics"; _resourcePath = [@"/topics" retain]; _resourceClass = [DBTopic class]; } @@ -56,6 +56,7 @@ NSMutableArray* items = [NSMutableArray arrayWithCapacity:[model.objects count]]; for(DBTopic* topic in model.objects) { + // TODO: RKGeneratePathWithObject. Move to postsTTURL method? NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts", topic.topicID]; [items addObject:[TTTableTextItem itemWithText:topic.name URL:url]]; } @@ -63,6 +64,7 @@ NSLog(@"Items: %@", items); // Ensure that the datasource's model is still the RKRequestTTModel; // Otherwise isOutdated will not work. + // TODO: Better fix for this? TTListDataSource* dataSource = [TTListDataSource dataSourceWithItems:items]; dataSource.model = model; self.dataSource = dataSource; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.h index 222cee4f..0650cd91 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.h @@ -9,6 +9,7 @@ #import #import +// TODO: Why not move this into a controller and inherit instead of using categories? @interface UIViewController (RKLoading) @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.m index f67dde09..f261daec 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/UIViewController+RKLoading.m @@ -12,7 +12,7 @@ @implementation UIViewController (RKLoading) - (void)requestDidStartLoad:(RKRequest*)request { - UIView* overlayView = [self.view viewWithTag:66]; + UIView* overlayView = [self.view viewWithTag:66]; // TODO: Need constant for tags... if (overlayView == nil) { overlayView = [[TTActivityLabel alloc] initWithFrame:self.view.bounds style:TTActivityLabelStyleBlackBox text:@"Loading..."]; overlayView.backgroundColor = [UIColor blackColor]; @@ -31,14 +31,14 @@ return; } progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(10, 100, 300, 20)]; - progressView.tag = 67; + progressView.tag = 67; // TODO: Need constant for tags... [self.view addSubview:progressView]; } progressView.progress = totalBytesWritten / (float)totalBytesExpectedToWrite; } - (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response { - UIView* overlayView = [self.view viewWithTag:66]; + UIView* overlayView = [self.view viewWithTag:66]; // TODO: Need constant for tags... UIView* progressView = [self.view viewWithTag:67]; [overlayView removeFromSuperview]; [overlayView release]; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h index 31d5fbea..d430a45c 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h @@ -9,23 +9,93 @@ #import #import +/** + * The Post models an individual piece of content posted to + * a Topic by a User within the Discussion Board. + */ @interface DBPost : RKManagedObject { UIImage* _newAttachment; } -@property (nonatomic, retain) NSString* attachmentContentType; -@property (nonatomic, retain) NSString* attachmentFileName; -@property (nonatomic, retain) NSNumber* attachmentFileSize; -@property (nonatomic, retain) NSString* attachmentPath; -@property (nonatomic, retain) NSDate* attachmentUpdatedAt; +//////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Content properties + +/** + * The textual content of the Post as entered by the User + */ @property (nonatomic, retain) NSString* body; + +/** + * A timestamp of when the Post was created + */ @property (nonatomic, retain) NSDate* createdAt; -@property (nonatomic, retain) NSNumber* topicID; + +/** + * A timestamp of when the Post was last updated + */ @property (nonatomic, retain) NSDate* updatedAt; + +//////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Relationship properties + +/** + * The numeric primary key to the Topic this Post was made to + * + * TODO: This is the primary key, used in the automatic primary key association. Document it. + */ +@property (nonatomic, retain) NSNumber* topicID; + +/** + * The numeric primary key to the User this Post was created by + * TODO: Create relationship and document + */ @property (nonatomic, retain) NSNumber* userID; + +/** + * The numeric primary key identifying this Post in the remote backend. This + * is the value used to uniquely identify this Post within the object store. + */ @property (nonatomic, retain) NSNumber* postID; + +/** + * The username of the User who created this Post + */ @property (nonatomic, retain) NSString* username; +//////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Attachment properties + +/** + * The MIME type of the attached file + */ +@property (nonatomic, retain) NSString* attachmentContentType; + +/** + * The filename of the attached file + */ +@property (nonatomic, retain) NSString* attachmentFileName; + +/** + * The size in bytes of the attached file + */ +@property (nonatomic, retain) NSNumber* attachmentFileSize; + +/** + * The filesystem path to the attached file on the remote system + */ +@property (nonatomic, retain) NSString* attachmentPath; + +/** + * A timestamp of the last time the attachment was modified (or created) + */ +@property (nonatomic, retain) NSDate* attachmentUpdatedAt; + +/** + * An accessor for supplying a new image to be attached to this Post + */ @property (nonatomic, retain) UIImage* newAttachment; +// TODO: Relationship to the User??? +// TODO: Relationship to the Topic??? + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m index 0c000287..fcffe4b2 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m @@ -26,6 +26,10 @@ @synthesize newAttachment = _newAttachment; +/** + * The property mapping dictionary. This method declares how elements in the JSON + * are mapped to properties on the object + */ + (NSDictionary*)elementToPropertyMappings { return [NSDictionary dictionaryWithKeysAndObjects: @"id",@"postID", @@ -43,11 +47,28 @@ nil]; } +/** + * Informs RestKit which property contains the primary key for identifying + * this object. This is used to ensure that objects are updated + */ + (NSString*)primaryKeyProperty { return @"postID"; } -- (id)paramsForSerialization { +// TODO: Fix encoding stuff with post[body] +// TODO: Fix bug. Can't edit stuff! +// TODO: paramsForGET / paramsForPOST / paramsForDELETE / paramsForPUT??? +/** + * Return a serializable representation of this object's properties. This + * serialization will be encoded by the router into a request body and + * sent to the remote service. + * + * A default implementation of paramsForSerialization is provided by the + * RKObject/RKManagedObject base classes, but can be overloaded in the subclass + * for customization. This is useful for including things like transient properties + * in your payloads. + */ +- (NSObject*)paramsForSerialization { RKParams* params = [RKParams params]; [params setValue:self.body forParam:@"post[body]"]; NSLog(@"Self Body: %@", self.body); diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h index 07c388be..5ab2ad78 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h @@ -9,15 +9,44 @@ #import #import +/** + * Models a Topic in the Discussion Board. Users can + * create Post's on Topics to have discussions. + */ @interface DBTopic : RKManagedObject { - + } +/** + * The numeric primary key for this topic in the remote backend system + */ @property (nonatomic, retain) NSNumber* topicID; + +/** + * The name of this Topic. Identifies what we are discussing + */ @property (nonatomic, retain) NSString* name; + +/** + * The numeric primary key of the User who created this Topic + */ @property (nonatomic, retain) NSNumber* userID; + +/** + * A timestamp of when the object was created + */ @property (nonatomic, retain) NSDate* createdAt; + +/** + * A timestamp of when the object was last modified + */ @property (nonatomic, retain) NSDate* updatedAt; + +/** + * The username of the User who created this Topic + */ @property (nonatomic, retain) NSString* username; +// TODO: Association with User + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m index c37fbdf7..076678cf 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m @@ -19,21 +19,30 @@ #pragma mark RKObjectMappable methods +/** + * The property mapping dictionary. This method declares how elements in the JSON + * are mapped to properties on the object. + */ + (NSDictionary*)elementToPropertyMappings { return [NSDictionary dictionaryWithKeysAndObjects: - @"id",@"topicID", - @"name",@"name", + @"id",@"topicID", + @"name",@"name", @"user_id",@"userID", - @"created_at",@"createdAt", + @"created_at",@"createdAt", @"updated_at",@"updatedAt", @"user_login", @"username", nil]; } +/** + * Informs RestKit which property contains the primary key for identifying + * this object. This is used to ensure that objects are updated + */ + (NSString*)primaryKeyProperty { return @"topicID"; } +// TODO: Eliminate this. Just use the Rails router - (id)paramsForSerialization { return [NSDictionary dictionaryWithObjectsAndKeys: self.name, @"topic[name]", nil]; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h index 73764619..368543ad 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h @@ -10,21 +10,53 @@ #import @interface DBUser : RKManagedObject { - // Transient. Used for login & signup + // Transient. Used for login & sign-up NSString* _password; NSString* _passwordConfirmation; } +/** + * The e-mail address of the User + */ @property (nonatomic, retain) NSString* email; + +/** + * The username of the User + */ +// TODO: Inconsistencies between username & login @property (nonatomic, retain) NSString* login; + // Access Token will only be populated on a logged in user. +/** + * An Access Token returned when a User is authenticated + */ +// TODO: Check design on this @property (nonatomic, retain) NSString* singleAccessToken; + +/** + * The numeric primary key of this User in the remote backend system + */ @property (nonatomic, retain) NSNumber* userID; +#pragma mark Transient sign-up properties + +/** + * The password the User wants to secure their account with + */ @property (nonatomic, retain) NSString* password; + +/** + * A confirmation of the password the User wants t secure their account with + */ @property (nonatomic, retain) NSString* passwordConfirmation; +/** + * A globally available singleton reference to the current User + */ + (DBUser*)currentUser; + +// TODO: Change to an instance method... + (void)logout; + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m index d91f28a2..2b8c5c34 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m @@ -8,7 +8,6 @@ #import "DBUser.h" - @implementation DBUser @dynamic email; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h index efb52530..06cc74cf 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h @@ -10,12 +10,24 @@ * General Constants */ +/** + * The Base URL constant. This Base URL is used to initialize RestKit via RKClient + * or RKObjectManager (which in turn initializes an instance of RKClient). The Base + * URL is used to build full URL's by appending a resource path onto the end. + * + * By abstracting your Base URL into an externally defined constant and utilizing + * conditional compilation, you can very quickly switch between server environments + * and produce builds targetted at different backend systems. + */ extern NSString* const kDBBaseURLString; +// TODO: Gets moved to DBUser as an internal constant extern NSString* const kCurrentUserIDKey; - +// TODO: Gets moved to DBUser extern NSString* const kUserLoggedInNotificationName; extern NSString* const kLoginCanceledNotificationName; extern NSString* const kUserLoggedOutNotificationName; -extern NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName; \ No newline at end of file + +// TODO: See if we can eliminate or abstract this further +extern NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.h index c8be32d7..faf0f9f7 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.h @@ -8,7 +8,11 @@ #import - +/** + * An implementation of the RestKit object cache. The object cache is + * used to return locally cached objects that live in a known resource path. + * This can be used to avoid trips to the network. + */ @interface DBManagedObjectCache : NSObject { } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m index f51a1fad..8db28595 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBManagedObjectCache.m @@ -12,6 +12,7 @@ @implementation DBManagedObjectCache +// TODO: Cleanup this code somehow... - (NSArray*)fetchRequestsForResourcePath:(NSString*)resourcePath { if ([resourcePath isEqualToString:@"/topics"]) { NSFetchRequest* request = [DBTopic fetchRequest]; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m index 8f5f83dd..e85d0bad 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m @@ -7,11 +7,17 @@ // #import "DiscussionBoardAppDelegate.h" + +// RestKit #import #import #import + +// Three20 #import #import + +// Discussion Board #import "DBTopicsTableViewController.h" #import "DBTopic.h" #import "DBPostsTableViewController.h" @@ -22,6 +28,7 @@ #import "DBUser.h" #import "DBPostTableViewController.h" +// TODO: Move this to the environment file? static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; @implementation DiscussionBoardAppDelegate @@ -31,44 +38,45 @@ static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; #pragma mark - #pragma mark Application lifecycle -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Initialize object manager RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:kDBBaseURLString]; - + // Set the default refresh rate to 1. This means we should always hit the web if we can. // If the server is unavailable, we will load from the core data cache. [RKRequestTTModel setDefaultRefreshRate:1]; - + // Do not overwrite properties that are missing in the payload to nil. objectManager.mapper.missingElementMappingPolicy = RKIgnoreMissingElementMappingPolicy; - + // Initialize object store objectManager.objectStore = [[[RKManagedObjectStore alloc] initWithStoreFilename:@"DiscussionBoard.sqlite"] autorelease]; objectManager.objectStore.managedObjectCache = [[DBManagedObjectCache new] autorelease]; - + // Set Up Mapper RKObjectMapper* mapper = objectManager.mapper; [mapper registerClass:[DBTopic class] forElementNamed:@"topic"]; [mapper registerClass:[DBPost class] forElementNamed:@"post"]; - + // Set Up Router + // TODO: Switch to Rails Router RKDynamicRouter* router = [[[RKDynamicRouter alloc] init] autorelease]; // RKRailsRouter* router = [[[RKRailsRouter alloc] init] autorelease]; [router routeClass:[DBUser class] toResourcePath:@"/signup" forMethod:RKRequestMethodPOST]; [router routeClass:[DBUser class] toResourcePath:@"/login" forMethod:RKRequestMethodPUT]; - + // [router setModelName:@"topic" forClass:[DBTopic class]]; [router routeClass:[DBTopic class] toResourcePath:@"/topics" forMethod:RKRequestMethodPOST]; [router routeClass:[DBTopic class] toResourcePath:@"/topics/(topicID)" forMethod:RKRequestMethodPUT]; [router routeClass:[DBTopic class] toResourcePath:@"/topics/(topicID)" forMethod:RKRequestMethodDELETE]; - + // [router setModelName:@"post" forClass:[DBPost class]]; [router routeClass:[DBPost class] toResourcePath:@"/topics/(topicID)/posts" forMethod:RKRequestMethodPOST]; [router routeClass:[DBPost class] toResourcePath:@"/topics/(topicID)/posts/(postID)" forMethod:RKRequestMethodPUT]; [router routeClass:[DBPost class] toResourcePath:@"/topics/(topicID)/posts/(postID)" forMethod:RKRequestMethodDELETE]; - + objectManager.router = router; - + // Initialize Three20 TTURLMap* map = [[TTNavigator navigator] URLMap]; [map from:@"db://topics" toViewController:[DBTopicsTableViewController class]]; @@ -78,15 +86,15 @@ static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; [map from:@"db://posts/(initWithPostID:)" toViewController:[DBPostTableViewController class]]; [map from:@"db://topics/(initWithTopicID:)/posts/new" toViewController:[DBPostTableViewController class]]; [map from:@"db://login" toModalViewController:[DBLoginViewController class]]; - - + + [map from:@"*" toViewController:[TTWebController class]]; - + [[TTURLRequestQueue mainQueue] setMaxContentLength:0]; // Don't limit content length. - + TTOpenURL(@"db://topics"); [[TTNavigator navigator].window makeKeyAndVisible]; - + // Authentication [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userLoggedIn:) name:kUserLoggedInNotificationName object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userLoggedOut:) name:kUserLoggedOutNotificationName object:nil]; @@ -94,10 +102,11 @@ static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; NSLog(@"Token: %@", user.singleAccessToken); NSLog(@"User: %@", user); [objectManager.client setValue:[DBUser currentUser].singleAccessToken forHTTPHeaderField:@"USER_ACCESS_TOKEN"]; - + return YES; } +// TODO: Move this login shit into the DBUser model - (void)userLoggedIn:(NSNotification*)note { RKObjectManager* objectManager = [RKObjectManager sharedManager]; [objectManager.client setValue:[DBUser currentUser].singleAccessToken forHTTPHeaderField:kAccessTokenHeaderField]; @@ -108,59 +117,9 @@ static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; [objectManager.client setValue:nil forHTTPHeaderField:kAccessTokenHeaderField]; } - -- (void)applicationWillResignActive:(UIApplication *)application { - /* - Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - */ -} - - -- (void)applicationDidEnterBackground:(UIApplication *)application { - /* - Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - If your application supports background execution, called instead of applicationWillTerminate: when the user quits. - */ -} - - -- (void)applicationWillEnterForeground:(UIApplication *)application { - /* - Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background. - */ -} - - -- (void)applicationDidBecomeActive:(UIApplication *)application { - /* - Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - */ -} - - -- (void)applicationWillTerminate:(UIApplication *)application { - /* - Called when the application is about to terminate. - See also applicationDidEnterBackground:. - */ -} - - -#pragma mark - -#pragma mark Memory management - -- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { - /* - Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later. - */ -} - - - (void)dealloc { [window release]; [super dealloc]; } - @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj b/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj index f0ab31d3..dc4ba022 100755 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 25359ED112E51070008BF33D /* DBManagedObjectCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 25359ECD12E51070008BF33D /* DBManagedObjectCache.m */; }; 25359ED212E51070008BF33D /* DiscussionBoardAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 25359ECF12E51070008BF33D /* DiscussionBoardAppDelegate.m */; }; 25359ED712E512BB008BF33D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 25359ED612E512BB008BF33D /* main.m */; }; + 25359F4112E53966008BF33D /* RKRequestTTModel+Loading.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F49C15A12DE1D9900FDE614 /* RKRequestTTModel+Loading.m */; }; 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; 3F22448812DDFCB30002559D /* UIViewController+RKLoading.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F22448712DDFCB30002559D /* UIViewController+RKLoading.m */; }; 3F255C9812DB70E200AFD2D1 /* DBPostsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255C9312DB70E200AFD2D1 /* DBPostsTableViewController.m */; }; @@ -38,7 +39,6 @@ 3F45615F12D7E31700BE25AD /* libRestKitObjectMapping.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3AD12D7822D00683D6F /* libRestKitObjectMapping.a */; }; 3F45616012D7E31800BE25AD /* libRestKitSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3AF12D7822D00683D6F /* libRestKitSupport.a */; }; 3F45616112D7E31900BE25AD /* libRestKitThree20.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3B712D7822D00683D6F /* libRestKitThree20.a */; }; - 3F49C15B12DE1D9900FDE614 /* RKRequestTTModel+Loading.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F49C15A12DE1D9900FDE614 /* RKRequestTTModel+Loading.m */; }; 3F76BBD612D7DA9D001562DC /* DataModel.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = 3F76BBD512D7DA9D001562DC /* DataModel.xcdatamodel */; }; A755038212E4CBD4009FC5EC /* icon-114.png in Resources */ = {isa = PBXBuildFile; fileRef = A755037712E4CBD4009FC5EC /* icon-114.png */; }; A755038312E4CBD4009FC5EC /* icon-57.png in Resources */ = {isa = PBXBuildFile; fileRef = A755037812E4CBD4009FC5EC /* icon-57.png */; }; @@ -1239,11 +1239,11 @@ 3F3239E112DBB9C600AB2F6E /* DBPostTableViewController.m in Sources */, 3F3239ED12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m in Sources */, 3F22448812DDFCB30002559D /* UIViewController+RKLoading.m in Sources */, - 3F49C15B12DE1D9900FDE614 /* RKRequestTTModel+Loading.m in Sources */, 25359ED012E51070008BF33D /* DBEnvironment.m in Sources */, 25359ED112E51070008BF33D /* DBManagedObjectCache.m in Sources */, 25359ED212E51070008BF33D /* DiscussionBoardAppDelegate.m in Sources */, 25359ED712E512BB008BF33D /* main.m in Sources */, + 25359F4112E53966008BF33D /* RKRequestTTModel+Loading.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1312,6 +1312,10 @@ "\"$(SRCROOT)\"", "\"$(SRCROOT)/Libraries/three20\"", ); + OTHER_LDFLAGS = ( + "-all_load", + "-ObjC", + ); PRODUCT_NAME = DiscussionBoard; VALIDATE_PRODUCT = YES; };