From 378aaac12b3eecdc4d724010876d5ebebf22b20d Mon Sep 17 00:00:00 2001 From: Jeremy Ellison Date: Tue, 11 Jan 2011 12:32:18 -0500 Subject: [PATCH] Topic and post creating, editing, and deleting --- Code/Three20/RKRequestTTModel.m | 8 +- .../DBAuthenticatedTableViewController.h | 17 ++ .../DBAuthenticatedTableViewController.m | 59 +++++++ .../Controllers/DBLoginViewController.h | 23 +++ .../Controllers/DBLoginViewController.m | 154 ++++++++++++++++++ .../Controllers/DBPostTableViewController.h | 23 +++ .../Controllers/DBPostTableViewController.m | 154 ++++++++++++++++++ .../DBPostsTableViewController.h | 6 +- .../Controllers/DBPostsTableViewController.m | 88 ++++++++++ .../DBResourceListTableViewController.h | 20 +++ .../DBResourceListTableViewController.m | 71 ++++++++ .../Controllers/DBTopicViewController.h | 18 ++ .../Controllers/DBTopicViewController.m | 97 +++++++++++ .../DBTopicsTableViewController.h | 4 +- .../Controllers/DBTopicsTableViewController.m | 65 ++++++++ .../DiscussionBoard/Classes/DBEnvironment.h | 8 + .../DiscussionBoard/Classes/DBEnvironment.m | 7 + .../Classes/DBManagedObjectCache.h | 16 ++ .../Classes/DBManagedObjectCache.m | 48 ++++++ .../Classes/DBPostsTableViewController.m | 49 ------ .../Classes/DBTopicsTableViewController.m | 34 ---- .../Classes/DiscussionBoardAppDelegate.m | 63 ++++++- .../Classes/{ => Models}/DBPost.h | 4 +- .../Classes/{ => Models}/DBPost.m | 15 ++ .../Classes/{ => Models}/DBTopic.h | 2 +- .../Classes/{ => Models}/DBTopic.m | 5 + .../DiscussionBoard/Classes/Models/DBUser.h | 30 ++++ .../DiscussionBoard/Classes/Models/DBUser.m | 67 ++++++++ .../DataModel.xcdatamodel/elements | Bin 29433 -> 37301 bytes .../DataModel.xcdatamodel/layout | Bin 5510 -> 6988 bytes .../DiscussionBoard.xcodeproj/project.pbxproj | 119 +++++++++++--- .../app/controllers/application_controller.rb | 2 +- .../app/controllers/topics_controller.rb | 2 +- .../app/controllers/users_controller.rb | 10 +- .../app/models/topic.rb | 3 + .../app/models/user.rb | 2 + 36 files changed, 1169 insertions(+), 124 deletions(-) create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.m create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.m create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.m rename Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/{ => Controllers}/DBPostsTableViewController.h (57%) create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.m create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.m create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.m rename Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/{ => Controllers}/DBTopicsTableViewController.h (62%) create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.m create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.m delete mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.m delete mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.m rename Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/{ => Models}/DBPost.h (90%) rename Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/{ => Models}/DBPost.m (65%) rename Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/{ => Models}/DBTopic.h (99%) rename Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/{ => Models}/DBTopic.m (82%) create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.h create mode 100644 Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.m diff --git a/Code/Three20/RKRequestTTModel.m b/Code/Three20/RKRequestTTModel.m index de467af6..844d40d9 100644 --- a/Code/Three20/RKRequestTTModel.m +++ b/Code/Three20/RKRequestTTModel.m @@ -10,6 +10,7 @@ #import "RKManagedObjectStore.h" #import "../Network/Network.h" + static NSTimeInterval defaultRefreshRate = NSTimeIntervalSince1970; static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTimeKey"; @@ -48,7 +49,7 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi } + (void)setDefaultRefreshRate:(NSTimeInterval)newDefaultRefreshRate { - defaultRefreshRate = defaultRefreshRate; + defaultRefreshRate = newDefaultRefreshRate; } - (id)initWithResourcePath:(NSString*)resourcePath { @@ -133,7 +134,8 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi } - (BOOL)isOutdated { - return (![self isLoading] && (-[self.loadedTime timeIntervalSinceNow] > _refreshRate)); + NSTimeInterval sinceNow = [self.loadedTime timeIntervalSinceNow]; + return (![self isLoading] && (-sinceNow > _refreshRate)); } - (void)cancel { @@ -240,7 +242,7 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi /////////////////////////////////////////////////////////////////////////////////////////////////// // public -- (void)load { +- (void)load { RKManagedObjectStore* store = [RKObjectManager sharedManager].objectStore; NSArray* cacheFetchRequests = nil; NSArray* cachedObjects = nil; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.h new file mode 100644 index 00000000..b84e0a88 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.h @@ -0,0 +1,17 @@ +// +// DBAuthenticatedTableViewController.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import + +@interface DBAuthenticatedTableViewController : TTTableViewController { + BOOL _requiresLoggedInUser; + NSNumber* _requiredUserID; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.m new file mode 100644 index 00000000..1aed44c3 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBAuthenticatedTableViewController.m @@ -0,0 +1,59 @@ +// +// DBAuthenticatedTableViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBAuthenticatedTableViewController.h" +#import "DBUser.h" + +@implementation DBAuthenticatedTableViewController + +- (void)viewDidUnload { + [[NSNotificationCenter defaultCenter] removeObserver:self name:kUserLoggedInNotificationName object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:kLoginCanceledNotificationName object:nil]; +} + +- (void)loadView { + [super loadView]; + + // Check if we are authenticated. If not, pop in login view controller. + if (_requiresLoggedInUser) { + BOOL isAuthenticated = NO; + if (_requiredUserID && + [DBUser currentUser].singleAccessToken && + // Put current user id first because it might be nil. + [[DBUser currentUser].userID isEqualToNumber:_requiredUserID]) { + isAuthenticated = YES; + } else if (_requiredUserID == nil && + [DBUser currentUser].singleAccessToken && + [DBUser currentUser] != nil) { + isAuthenticated = YES; + } + if (!isAuthenticated) { + // Register for login succeeded notification. populate view. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidLogin:) name:kUserLoggedInNotificationName object:nil]; + // Register for login canceled notification. pop view controller. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginCanceled:) name:kLoginCanceledNotificationName object:nil]; + TTOpenURL(@"db://login"); + } + } +} + +- (void)userDidLogin:(NSNotification*)note { + // check user id is allowed. + if (_requiredUserID && [[DBUser currentUser].userID isEqualToNumber:_requiredUserID]) { + [self invalidateModel]; + } else { + [self.navigationController popViewControllerAnimated:YES]; + } +} + +- (void)loginCanceled:(NSNotification*)note { + [self.navigationController popViewControllerAnimated:YES]; +} + + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.h new file mode 100644 index 00000000..3ca5722e --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.h @@ -0,0 +1,23 @@ +// +// DBLoginViewController.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import +#import + +@interface DBLoginViewController : TTTableViewController { + UIBarButtonItem* _signupOrLoginButtonItem; + BOOL _showingSignup; + + UITextField* _usernameField; + UITextField* _passwordField; + UITextField* _passwordConfirmationField; + UITextField* _emailField; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.m new file mode 100644 index 00000000..4ce7f40b --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBLoginViewController.m @@ -0,0 +1,154 @@ +// +// DBLoginViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBLoginViewController.h" +#import "DBUser.h" + +@implementation DBLoginViewController + +- (id)initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query { + if (self = [super initWithNavigatorURL:URL query:query]) { + self.title = @"Login"; + self.autoresizesForKeyboard = YES; + self.tableViewStyle = UITableViewStyleGrouped; + } + return self; +} + +- (void)viewDidUnload { + TT_RELEASE_SAFELY(_signupOrLoginButtonItem); + TT_RELEASE_SAFELY(_usernameField); + TT_RELEASE_SAFELY(_passwordField); + TT_RELEASE_SAFELY(_passwordConfirmationField); + TT_RELEASE_SAFELY(_emailField); +} + +- (void)loadView { + [super loadView]; + + UIBarButtonItem* cancelItem = [[[UIBarButtonItem alloc] initWithTitle:@"Cancel" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(cancelButtonWasPressed:)] autorelease]; + self.navigationItem.leftBarButtonItem = cancelItem; + + _signupOrLoginButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Signup" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(signupOrLoginButtonItemWasPressed:)]; + _showingSignup = NO; + self.navigationItem.rightBarButtonItem = _signupOrLoginButtonItem; + + _usernameField = [[UITextField alloc] initWithFrame:CGRectZero]; + _usernameField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _usernameField.delegate = self; + _usernameField.returnKeyType = UIReturnKeyNext; + + _passwordField = [[UITextField alloc] initWithFrame:CGRectZero]; + [_passwordField setSecureTextEntry:YES]; + _passwordField.delegate = self; + + _passwordConfirmationField = [[UITextField alloc] initWithFrame:CGRectZero]; + [_passwordConfirmationField setSecureTextEntry:YES]; + _passwordConfirmationField.delegate = self; + _passwordConfirmationField.returnKeyType = UIReturnKeyGo; + + _emailField = [[UITextField alloc] initWithFrame:CGRectZero]; + _emailField.autocapitalizationType = UITextAutocapitalizationTypeNone; + _emailField.delegate = self; + _emailField.returnKeyType = UIReturnKeyNext; +} + +- (void)createModel { + NSMutableArray* items = [NSMutableArray array]; + if (_showingSignup) { + [items addObject:[TTTableControlItem itemWithCaption:@"Username" control:_usernameField]]; + [items addObject:[TTTableControlItem itemWithCaption:@"Email" control:_emailField]]; + [items addObject:[TTTableControlItem itemWithCaption:@"Password" control:_passwordField]]; + [items addObject:[TTTableControlItem itemWithCaption:@"Confirm" control:_passwordConfirmationField]]; + _passwordField.returnKeyType = UIReturnKeyNext; + } else { + [items addObject:[TTTableControlItem itemWithCaption:@"Username" control:_usernameField]]; + [items addObject:[TTTableControlItem itemWithCaption:@"Password" control:_passwordField]]; + _passwordField.returnKeyType = UIReturnKeyGo; + } + self.dataSource = [TTListDataSource dataSourceWithItems:items]; + [_usernameField becomeFirstResponder]; +} + +- (void)cancelButtonWasPressed:(id)sender { + [self dismissModalViewControllerAnimated:YES]; + [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCanceledNotificationName object:self]; +} + +- (void)signupOrLoginButtonItemWasPressed:(id)sender { + if (_showingSignup) { + _showingSignup = NO; + [_signupOrLoginButtonItem setTitle:@"Login"]; + } else { + _showingSignup = YES; + [_signupOrLoginButtonItem setTitle:@"Signup"]; + } + [self invalidateModel]; +} + +- (void)loginOrSignup { + if (_showingSignup) { + // Signup + DBUser* user = [DBUser object]; + user.login = _usernameField.text; + user.email = _emailField.text; + user.password = _passwordField.text; + user.passwordConfirmation = _passwordConfirmationField.text; + + [[RKObjectManager sharedManager] postObject:user delegate:self]; + } else { + // Login + DBUser* user = [DBUser object]; + user.login = _usernameField.text; + user.password = _passwordField.text; + [[RKObjectManager sharedManager] putObject:user delegate:self]; + } +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + if (textField == _usernameField) { + if (_showingSignup) { + [_emailField becomeFirstResponder]; + } else { + [_passwordField becomeFirstResponder]; + } + } else if (textField == _passwordField) { + if (_showingSignup) { + [_passwordConfirmationField becomeFirstResponder]; + } else { + [self loginOrSignup]; + } + } else if (textField == _emailField) { + [_passwordField becomeFirstResponder]; + } else if (textField == _passwordConfirmationField) { + [self loginOrSignup]; + } + return NO; +} + +- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects { + assert([objects count] == 1); + DBUser* user = [objects objectAtIndex:0]; + NSLog(@"Authentication Token: %@", user.singleAccessToken); + [[NSUserDefaults standardUserDefaults] setObject:user.userID forKey:kCurrentUserIDKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedInNotificationName object:user]; + [self dismissModalViewControllerAnimated:YES]; +} + +- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error { + [[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.h new file mode 100644 index 00000000..b6fc9772 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.h @@ -0,0 +1,23 @@ +// +// DBPostTableViewController.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "DBAuthenticatedTableViewController.h" +#import "DBPost.h" + +@interface DBPostTableViewController : DBAuthenticatedTableViewController { + DBPost* _post; + NSNumber* _topicID; + + TTTextEditor* _bodyTextEditor; + TTImageView* _currentAttachmentImageView; + + UIImage* _newAttachment; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.m new file mode 100644 index 00000000..dc6b0d4c --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostTableViewController.m @@ -0,0 +1,154 @@ +// +// DBPostTableViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBPostTableViewController.h" +#import + +@implementation DBPostTableViewController + +- (id)initWithPostID:(NSString*)postID { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + _post = [[DBPost objectWithPrimaryKeyValue:postID] retain]; + } + return self; +} + +- (id)initWithTopicID:(NSString*)topicID { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + _topicID = [[NSNumber numberWithInt:[topicID intValue]] retain]; + } + return self; +} + +- (void)dealloc { + [_post release]; + [_topicID release]; + [super dealloc]; +} + +- (BOOL)isNewRecord { + return [[_post postID] intValue] == 0; +} + +- (void)viewDidUnload { + TT_RELEASE_SAFELY(_bodyTextEditor); + [[TTNavigator navigator].URLMap removeURL:@"db://updateAttachment"]; +} + +- (void)loadView { + self.tableViewStyle = UITableViewStyleGrouped; + self.autoresizesForKeyboard = YES; + self.variableHeightRows = YES; + [[TTNavigator navigator].URLMap from:@"db://updateAttachment" toObject:self selector:@selector(updateAttachment)]; + if (nil == _post) { + _post = [[DBPost object] retain]; + _post.topicID = _topicID; + } + + _requiresLoggedInUser = YES; + if (![self isNewRecord]) { + _requiredUserID = _post.userID; + } + + [super loadView]; + + _bodyTextEditor = [[TTTextEditor alloc] initWithFrame:CGRectMake(0, 0, 300, 120)]; + _bodyTextEditor.font = [UIFont systemFontOfSize:12]; + _bodyTextEditor.autoresizesToText = NO; + _bodyTextEditor.delegate = self; +} + +- (void)createModel { + NSMutableArray* items = [NSMutableArray array]; + + _bodyTextEditor.text = _post.body; + + [items addObject:[TTTableControlItem itemWithCaption:@"" control:(UIControl*)_bodyTextEditor]]; + + // Attachment item. + if (_newAttachment) { + // 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]) { + // Has existing attachment. allow replace + NSString* url = [NSString stringWithFormat:@"%@%@", [RKObjectManager sharedManager].client.baseURL, _post.attachmentPath]; + [items addObject:[TTTableImageItem itemWithText:@"Tap to Replace Image" imageURL:url defaultImage:nil URL:@"db://updateAttachment"]]; + } else { + // has no attachment. allow new one. + [items addObject:[TTTableTextItem itemWithText:@"Tap to Add Image" URL:@"db://updateAttachment"]]; + } + + if ([self isNewRecord]) { + self.title = @"New Post"; + [items addObject:[TTTableButton itemWithText:@"Create" delegate:self selector:@selector(createButtonWasPressed:)]]; + } else { + self.title = @"Edit Post"; + [items addObject:[TTTableButton itemWithText:@"Update" delegate:self selector:@selector(updateButtonWasPressed:)]]; + [items addObject:[TTTableButton itemWithText:@"Delete" delegate:self selector:@selector(destroyButtonWasPressed:)]]; + } + self.dataSource = [TTListDataSource dataSourceWithItems:items]; +} + +- (void)updateAttachment { + UIImagePickerController* controller = [[[UIImagePickerController alloc] init] autorelease]; + controller.delegate = self; + [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]; +} + +- (void)createButtonWasPressed:(id)sender { + _post.body = _bodyTextEditor.text; + _post.newAttachment = _newAttachment; + [[RKObjectManager sharedManager] postObject:_post delegate:self]; +} + +- (void)updateButtonWasPressed:(id)sender { + _post.body = _bodyTextEditor.text; + _post.newAttachment = _newAttachment; + [[RKObjectManager sharedManager] putObject:_post delegate:self]; +} + +- (void)destroyButtonWasPressed:(id)sender { + [[RKObjectManager sharedManager] deleteObject:_post delegate:self]; +} + +- (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. + [[NSNotificationCenter defaultCenter] postNotificationName:kObjectCreatedUpdatedOrDestroyedNotificationName object:objects]; + // dismiss. + [self.navigationController popViewControllerAnimated:YES]; +} + +- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error { + [[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; +} + +// Text Editor Delegate + +- (void)textEditorDidBeginEditing:(TTTextEditor*)textEditor { + UIBarButtonItem* doneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:textEditor action:@selector(resignFirstResponder)]; + [doneButton autorelease]; + self.navigationItem.rightBarButtonItem = doneButton; +} + +- (void)textEditorDidEndEditing:(TTTextEditor*)textEditor { + self.navigationItem.rightBarButtonItem = nil; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.h similarity index 57% rename from Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.h rename to Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.h index c03de01d..7df31fb0 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.h @@ -8,9 +8,13 @@ #import #import +#import "DBResourceListTableViewController.h" +#import "DBTopic.h" -@interface DBPostsTableViewController : TTTableViewController { +@interface DBPostsTableViewController : DBResourceListTableViewController { NSString* _topicID; } +@property (nonatomic, readonly) DBTopic* topic; + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.m new file mode 100644 index 00000000..678d263e --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBPostsTableViewController.m @@ -0,0 +1,88 @@ +// +// DBPostsTableViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/7/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBPostsTableViewController.h" +#import "DBPost.h" +#import +#import "DBUser.h" + +@implementation DBPostsTableViewController + +- (id)initWithTopicID:(NSString*)topicID { + if (self = [super initWithStyle:UITableViewStylePlain]) { + _topicID = [topicID retain]; + + self.title = @"Posts"; + _resourcePath = [[NSString stringWithFormat:@"/topics/%@/posts", _topicID] retain]; + _resourceClass = [DBPost class]; + } + return self; +} + +- (void)dealloc { + [_topicID release]; + [super dealloc]; +} + +- (void)loadView { + [super loadView]; + self.variableHeightRows = YES; + + UIBarButtonItem* newItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonWasPressed:)] autorelease]; + self.navigationItem.rightBarButtonItem = newItem; +} + +- (void)addButtonWasPressed:(id)sender { + NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts/new", _topicID]; + TTOpenURL(url); +} + +- (void)createModel { + if (nil == [DBTopic objectWithPrimaryKeyValue:_topicID]) { + // 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]]; + NSMutableArray* topicItems = [NSMutableArray arrayWithCapacity:2]; + + [topicItems addObject:[TTTableTextItem itemWithText:self.topic.name]]; + // only add edit item if there is no current user (lazy login) or + // the current user id == topic user id. + NSNumber* topicUserId = self.topic.userID; + // if topicUserId is nil, the topic has no user for some reason (perhaps they got deleted). + if ([DBUser currentUser] == nil || + (topicUserId && [[DBUser currentUser].userID isEqualToNumber:topicUserId])) { + NSString* editURL = [NSString stringWithFormat:@"db://topics/%@/edit", _topicID]; + [topicItems addObject:[TTTableTextItem itemWithText:@"Edit" URL:editURL]]; + } + + for(DBPost* post in model.objects) { + NSString* url = [NSString stringWithFormat:@"db://posts/%@", post.postID]; + [postItems addObject:[TTTableLongTextItem itemWithText:post.body URL:url]]; + } + + self.dataSource = [TTSectionedDataSource dataSourceWithArrays:@"Topic", + topicItems, + @"Posts", + postItems, nil]; + } +} + +- (DBTopic*)topic { + return [DBTopic objectWithPrimaryKeyValue:_topicID]; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.h new file mode 100644 index 00000000..ae38abcb --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.h @@ -0,0 +1,20 @@ +// +// DBResourceListTableViewController.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import +#import + +@interface DBResourceListTableViewController : TTTableViewController { + UILabel* _loadedAtLabel; + + NSString* _resourcePath; + Class _resourceClass; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.m new file mode 100644 index 00000000..17905ab5 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBResourceListTableViewController.m @@ -0,0 +1,71 @@ +// +// DBResourceListTableViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBResourceListTableViewController.h" +#import "DBManagedObjectCache.h" + +@implementation DBResourceListTableViewController + +- (void)loadView { + [super loadView]; + + UIView* tableHeaderView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 90)] autorelease]; + _loadedAtLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 20)]; + _loadedAtLabel.textAlignment = UITextAlignmentCenter; + [tableHeaderView addSubview:_loadedAtLabel]; + UIButton* reloadButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; + [reloadButton setTitle:@"Reload" forState:UIControlStateNormal]; + [reloadButton addTarget:self action:@selector(reloadButtonWasPressed:) forControlEvents:UIControlEventTouchUpInside]; + reloadButton.frame = CGRectMake(100, 40, 100, 40); + [tableHeaderView addSubview:reloadButton]; + + self.tableView.tableHeaderView = tableHeaderView; + + [[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]; +} + +- (void)reloadButtonWasPressed:(id)sender { + [self invalidateModel]; +} + +- (void)viewDidUnload { + [_loadedAtLabel release]; + _loadedAtLabel = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)reloadNotification:(NSNotification*)note { + [self invalidateModel]; +} + +- (void)userStateChanged:(NSNotification*)note { + [self invalidateModel]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; +} + +- (void)createModel { + self.model = [[[RKRequestTTModel alloc] initWithResourcePath:_resourcePath] autorelease]; +} + +- (void)didLoadModel:(BOOL)firstTime { + if ([self.model isKindOfClass:[RKRequestTTModel class]]) { + RKRequestTTModel* model = (RKRequestTTModel*)self.model; + + NSDateFormatter* formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"hh:mm:ss MM/dd/yy"]; + _loadedAtLabel.text = [NSString stringWithFormat:@"Loaded At: %@", [formatter stringFromDate:model.loadedTime]]; + } +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.h new file mode 100644 index 00000000..e797382b --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.h @@ -0,0 +1,18 @@ +// +// DBEditTopicViewController.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import "DBAuthenticatedTableViewController.h" +#import "DBTopic.h" + +@interface DBTopicViewController : DBAuthenticatedTableViewController { + UITextField* _topicNameField; + DBTopic* _topic; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.m new file mode 100644 index 00000000..c09b40a4 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicViewController.m @@ -0,0 +1,97 @@ +// +// DBEditTopicViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBTopicViewController.h" +#import "DBTopic.h" +#import "DBUser.h" + +@implementation DBTopicViewController + +- (id)initWithTopicID:(NSString*)topicID { + if (self = [super initWithStyle:UITableViewStyleGrouped]) { + _topic = [[DBTopic objectWithPrimaryKeyValue:topicID] retain]; + } + return self; +} + +- (void)dealloc { + [_topic release]; + [super dealloc]; +} + +- (BOOL)isNewRecord { + return [[_topic topicID] intValue] == 0; +} + +- (void)viewDidUnload { + TT_RELEASE_SAFELY(_topicNameField); +} + +- (void)loadView { + self.tableViewStyle = UITableViewStyleGrouped; + + if (nil == _topic) { + _topic = [[DBTopic object] retain]; + _topic.name = @""; + } + + _requiresLoggedInUser = YES; + if (![self isNewRecord]) { + _requiredUserID = _topic.userID; + } + + [super loadView]; + + _topicNameField = [[UITextField alloc] initWithFrame:CGRectZero]; +} + +- (void)createModel { + NSMutableArray* items = [NSMutableArray array]; + + _topicNameField.text = _topic.name; + [items addObject:[TTTableControlItem itemWithCaption:@"Name" control:_topicNameField]]; + + if ([self isNewRecord]) { + self.title = @"New Topic"; + [items addObject:[TTTableButton itemWithText:@"Create" delegate:self selector:@selector(createButtonWasPressed:)]]; + } else { + self.title = @"Edit Topic"; + [items addObject:[TTTableButton itemWithText:@"Update" delegate:self selector:@selector(updateButtonWasPressed:)]]; + [items addObject:[TTTableButton itemWithText:@"Delete" delegate:self selector:@selector(destroyButtonWasPressed:)]]; + } + self.dataSource = [TTListDataSource dataSourceWithItems:items]; +} + +- (void)createButtonWasPressed:(id)sender { + _topic.name = _topicNameField.text; + [[RKObjectManager sharedManager] postObject:_topic delegate:self]; +} + +- (void)updateButtonWasPressed:(id)sender { + _topic.name = _topicNameField.text; + [[RKObjectManager sharedManager] putObject:_topic delegate:self]; +} + +- (void)destroyButtonWasPressed:(id)sender { + [[RKObjectManager sharedManager] deleteObject:_topic delegate:self]; +} + +- (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. + [[NSNotificationCenter defaultCenter] postNotificationName:kObjectCreatedUpdatedOrDestroyedNotificationName object:objects]; + // dismiss. + [self.navigationController popViewControllerAnimated:YES]; +} + +- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error { + [[[[UIAlertView alloc] initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.h similarity index 62% rename from Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.h rename to Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.h index 3dcb3276..b93b5b10 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.h @@ -7,9 +7,9 @@ // #import -#import +#import "DBResourceListTableViewController.h" -@interface DBTopicsTableViewController : TTTableViewController { +@interface DBTopicsTableViewController : DBResourceListTableViewController { } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.m new file mode 100644 index 00000000..5e1babfe --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Controllers/DBTopicsTableViewController.m @@ -0,0 +1,65 @@ +// +// DBTopicsTableViewController.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/7/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBTopicsTableViewController.h" +#import "DBTopic.h" +#import +#import "DBUser.h" + +@implementation DBTopicsTableViewController + +- (id)initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query { + if (self = [super initWithNavigatorURL:URL query:query]) { + self.title = @"Topics"; + _resourcePath = [@"/topics" retain]; + _resourceClass = [DBTopic class]; + } + return self; +} + +- (void)createModel { + [super createModel]; + + UIBarButtonItem* item = nil; + if ([DBUser currentUser]) { + item = [[[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStyleBordered target:self action:@selector(logoutButtonWasPressed:)] autorelease]; + } + self.navigationItem.leftBarButtonItem = item; + + UIBarButtonItem* newItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonWasPressed:)] autorelease]; + self.navigationItem.rightBarButtonItem = newItem; +} + +- (void)addButtonWasPressed:(id)sender { + TTOpenURL(@"db://topics/new"); +} + +- (void)logoutButtonWasPressed:(id)sender { + [DBUser logout]; +} + +- (void)didLoadModel:(BOOL)firstTime { + [super didLoadModel:firstTime]; + RKRequestTTModel* model = (RKRequestTTModel*)self.model; + + NSMutableArray* items = [NSMutableArray arrayWithCapacity:[model.objects count]]; + + for(DBTopic* topic in model.objects) { + NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts", topic.topicID]; + [items addObject:[TTTableTextItem itemWithText:topic.name URL:url]]; + } + + NSLog(@"Items: %@", items); + // Ensure that the datasource's model is still the RKRequestTTModel; + // Otherwise isOutdated will not work. + TTListDataSource* dataSource = [TTListDataSource dataSourceWithItems:items]; + dataSource.model = model; + self.dataSource = dataSource; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.h index 78d04610..efb52530 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.h @@ -11,3 +11,11 @@ */ extern NSString* const kDBBaseURLString; + +extern NSString* const kCurrentUserIDKey; + + +extern NSString* const kUserLoggedInNotificationName; +extern NSString* const kLoginCanceledNotificationName; +extern NSString* const kUserLoggedOutNotificationName; +extern NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName; \ No newline at end of file diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.m index b3804532..4fac5784 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBEnvironment.m @@ -10,3 +10,10 @@ NSString* const kDBBaseURLString = @"http://localhost:3000"; //NSString* const kDBBaseURLString = @"http://discussionboard.heroku.com"; + +NSString* const kCurrentUserIDKey = @"currentUserKey"; + +NSString* const kUserLoggedInNotificationName = @"kUserLoggedInNotificationName"; +NSString* const kLoginCanceledNotificationName = @"kLoginCanceledNotificationName"; +NSString* const kUserLoggedOutNotificationName = @"kUserLoggedOutNotificationName"; +NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName = @"kObjectCreatedUpdatedOrDestroyedNotificationName"; \ No newline at end of file diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.h new file mode 100644 index 00000000..cc3b7a8b --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.h @@ -0,0 +1,16 @@ +// +// DBManagedObjectCache.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface DBManagedObjectCache : NSObject { + +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.m new file mode 100644 index 00000000..cec14ae2 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBManagedObjectCache.m @@ -0,0 +1,48 @@ +// +// DBManagedObjectCache.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBManagedObjectCache.h" +#import "DBTopic.h" +#import "DBPost.h" + +@implementation DBManagedObjectCache + +- (NSArray*)fetchRequestsForResourcePath:(NSString*)resourcePath { + // Don't return anything from the cache if we are online.. + // If we return cached objects, RestKit assumes they are up to date + // and does not hit the server. + +// if ([[RKObjectManager sharedManager] isOnline]) { +// return nil; +// } + + if ([resourcePath isEqualToString:@"/topics"]) { + NSFetchRequest* request = [DBTopic fetchRequest]; + NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:YES]; + [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; + return [NSArray arrayWithObject:request]; + } + // match on /topics/:id/posts + NSArray* components = [resourcePath componentsSeparatedByString:@"/"]; + if ([components count] == 4 && + [[components objectAtIndex:1] isEqualToString:@"topics"] && + [[components objectAtIndex:3] isEqualToString:@"posts"]) { + NSString* topicIDString = [components objectAtIndex:2]; + NSNumber* topicID = [NSNumber numberWithInt:[topicIDString intValue]]; + NSFetchRequest* request = [DBPost fetchRequest]; + NSPredicate* predicate = [NSPredicate predicateWithFormat:@"topicID = %@", topicID, nil]; + [request setPredicate:predicate]; + NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:YES]; + [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; + return [NSArray arrayWithObject:request]; + } + + return nil; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.m deleted file mode 100644 index 1d5db896..00000000 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPostsTableViewController.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// DBPostsTableViewController.m -// DiscussionBoard -// -// Created by Jeremy Ellison on 1/7/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "DBPostsTableViewController.h" -#import "DBPost.h" -#import - -@implementation DBPostsTableViewController - -- (id)initWithTopicID:(NSString*)topicID { - if (self = [super initWithStyle:UITableViewStylePlain]) { - _topicID = [topicID retain]; - } - return self; -} - -- (void)dealloc { - [_topicID release]; - [super dealloc]; -} - -- (void)loadView { - [super loadView]; - self.title = @"Posts"; - self.variableHeightRows = YES; - - NSString* path = [NSString stringWithFormat:@"/topics/%@/posts", _topicID]; - self.model = [[[RKRequestTTModel alloc] initWithResourcePath:path] autorelease]; -} - -- (void)didLoadModel:(BOOL)firstTime { - RKRequestTTModel* model = (RKRequestTTModel*)self.model; - NSMutableArray* items = [NSMutableArray arrayWithCapacity:[model.objects count]]; - - for(DBPost* post in model.objects) { - NSString* url = @""; - [items addObject:[TTTableLongTextItem itemWithText:post.body URL:url]]; - } - - NSLog(@"Items: %@", items); - self.dataSource = [TTListDataSource dataSourceWithItems:items]; -} - -@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.m deleted file mode 100644 index eb0aadb5..00000000 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopicsTableViewController.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// DBTopicsTableViewController.m -// DiscussionBoard -// -// Created by Jeremy Ellison on 1/7/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "DBTopicsTableViewController.h" -#import -#import "DBTopic.h" - -@implementation DBTopicsTableViewController - -- (void)loadView { - [super loadView]; - self.title = @"Topics"; - - self.model = [[[RKRequestTTModel alloc] initWithResourcePath:@"/topics"] autorelease]; -} - -- (void)didLoadModel:(BOOL)firstTime { - RKRequestTTModel* model = (RKRequestTTModel*)self.model; - NSMutableArray* items = [NSMutableArray arrayWithCapacity:[model.objects count]]; - - for(DBTopic* topic in model.objects) { - NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts", topic.topicID]; - [items addObject:[TTTableTextItem itemWithText:topic.name URL:url]]; - } - NSLog(@"Items: %@", items); - self.dataSource = [TTListDataSource dataSourceWithItems:items]; -} - -@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DiscussionBoardAppDelegate.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DiscussionBoardAppDelegate.m index 23bbfef8..1704e191 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DiscussionBoardAppDelegate.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DiscussionBoardAppDelegate.m @@ -8,18 +8,25 @@ #import "DiscussionBoardAppDelegate.h" #import +#import #import #import #import "DBTopicsTableViewController.h" #import "DBTopic.h" #import "DBPostsTableViewController.h" #import "DBPost.h" +#import "DBManagedObjectCache.h" +#import "DBTopicViewController.h" +#import "DBLoginViewController.h" +#import "DBUser.h" +#import "DBPostTableViewController.h" + +static NSString* const kAccessTokenHeaderField = @"HTTP_USER_ACCESS_TOKEN"; @implementation DiscussionBoardAppDelegate @synthesize window; - #pragma mark - #pragma mark Application lifecycle @@ -27,25 +34,77 @@ // 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 + RKDynamicRouter* router = [[[RKDynamicRouter alloc] init] autorelease]; + [router routeClass:[DBUser class] toResourcePath:@"/signup" forMethod:RKRequestMethodPOST]; + [router routeClass:[DBUser class] toResourcePath:@"/login" forMethod:RKRequestMethodPUT]; + + [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 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]]; [map from:@"db://topics/(initWithTopicID:)/posts" toViewController:[DBPostsTableViewController class]]; + [map from:@"db://topics/(initWithTopicID:)/edit" toViewController:[DBTopicViewController class]]; + [map from:@"db://topics/new" toViewController:[DBTopicViewController class]]; + [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]]; 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]; + DBUser* user = [DBUser currentUser]; + NSLog(@"Token: %@", user.singleAccessToken); + NSLog(@"User: %@", user); + [objectManager.client setValue:[DBUser currentUser].singleAccessToken forHTTPHeaderField:@"USER_ACCESS_TOKEN"]; + + // Testing + TTOpenURL(@"db://posts/9"); + return YES; } +- (void)userLoggedIn:(NSNotification*)note { + RKObjectManager* objectManager = [RKObjectManager sharedManager]; + [objectManager.client setValue:[DBUser currentUser].singleAccessToken forHTTPHeaderField:kAccessTokenHeaderField]; +} + +- (void)userLoggedOut:(NSNotification*)note { + RKObjectManager* objectManager = [RKObjectManager sharedManager]; + [objectManager.client setValue:nil forHTTPHeaderField:kAccessTokenHeaderField]; +} + - (void)applicationWillResignActive:(UIApplication *)application { /* diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPost.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBPost.h similarity index 90% rename from Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPost.h rename to Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBPost.h index 838a9915..97853d3f 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPost.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBPost.h @@ -10,7 +10,7 @@ #import @interface DBPost : RKManagedObject { - + UIImage* _newAttachment; } @property (nonatomic, retain) NSString* attachmentContentType; @@ -25,4 +25,6 @@ @property (nonatomic, retain) NSNumber* userID; @property (nonatomic, retain) NSNumber* postID; +@property (nonatomic, retain) UIImage* newAttachment; + @end \ No newline at end of file diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPost.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBPost.m similarity index 65% rename from Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPost.m rename to Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBPost.m index 569e2570..f0069102 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBPost.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBPost.m @@ -23,6 +23,8 @@ @dynamic userID; @dynamic postID; +@synthesize newAttachment = _newAttachment; + + (NSDictionary*)elementToPropertyMappings { return [NSDictionary dictionaryWithKeysAndObjects: @"id",@"postID", @@ -43,4 +45,17 @@ return @"postID"; } +- (NSDictionary*)paramsForSerialization { + RKParams* params = [RKParams params]; + [params setValue:self.body forParam:@"post[body]"]; + if (_newAttachment) { + NSData* data = UIImagePNGRepresentation(_newAttachment); + NSLog(@"Data Size: %d", [data length]); + [params setData:data MIMEType:@"application/octet-stream" forParam:@"topic[attachment]"]; + } + + // Suppress warning. todo: should this method return an by default? + return (NSDictionary*)params; +} + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopic.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBTopic.h similarity index 99% rename from Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopic.h rename to Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBTopic.h index eb4aeac6..eb74abb5 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopic.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBTopic.h @@ -10,7 +10,7 @@ #import @interface DBTopic : RKManagedObject { - + } @property (nonatomic, retain) NSNumber* topicID; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopic.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBTopic.m similarity index 82% rename from Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopic.m rename to Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBTopic.m index ae1d89d6..bdac5472 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/DBTopic.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBTopic.m @@ -32,5 +32,10 @@ return @"topicID"; } +- (NSDictionary*)paramsForSerialization { + return [NSDictionary dictionaryWithObjectsAndKeys: + self.name, @"topic[name]", nil]; +} + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.h new file mode 100644 index 00000000..f45f67b9 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.h @@ -0,0 +1,30 @@ +// +// DBUser.h +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import +#import + +@interface DBUser : RKManagedObject { + // Transient. Used for login & signup + NSString* _password; + NSString* _passwordConfirmation; +} + +@property (nonatomic, retain) NSString* email; +@property (nonatomic, retain) NSString* login; +// Access Token will only be populated on a logged in user. +@property (nonatomic, retain) NSString* singleAccessToken; +@property (nonatomic, retain) NSNumber* userID; + +@property (nonatomic, retain) NSString* password; +@property (nonatomic, retain) NSString* passwordConfirmation; + ++ (DBUser*)currentUser; ++ (void)logout; + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.m new file mode 100644 index 00000000..1282bc42 --- /dev/null +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Classes/Models/DBUser.m @@ -0,0 +1,67 @@ +// +// DBUser.m +// DiscussionBoard +// +// Created by Jeremy Ellison on 1/10/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "DBUser.h" + + +@implementation DBUser + +@dynamic email; +@dynamic login; +@dynamic singleAccessToken; +@dynamic userID; + +@synthesize password = _password; +@synthesize passwordConfirmation = _passwordConfirmation; + ++ (NSDictionary*)elementToPropertyMappings { + return [NSDictionary dictionaryWithKeysAndObjects: + @"id",@"userID", + @"email",@"email", + @"login",@"login", + @"single_access_token",@"singleAccessToken", + @"password", @"password", + @"password_confirmation", @"passwordConfirmation", + nil]; +} + ++ (NSString*)primaryKeyProperty { + return @"userID"; +} + ++ (DBUser*)currentUser { + id userID = [[NSUserDefaults standardUserDefaults] objectForKey:kCurrentUserIDKey]; + return [self objectWithPrimaryKeyValue:userID]; +} + ++ (void)logout { + [[NSUserDefaults standardUserDefaults] setValue:nil forKey:kCurrentUserIDKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedOutNotificationName object:nil]; +} + +- (void)dealloc { + [_password release]; + [_passwordConfirmation release]; + [super dealloc]; +} + +- (NSDictionary*)paramsForSerialization { + if (_passwordConfirmation) { + return [NSDictionary dictionaryWithObjectsAndKeys: + self.email, @"user[email]", + self.login, @"user[login]", + self.password, @"user[password]", + self.passwordConfirmation, @"user[password_confirmation]", nil]; + } + return [NSDictionary dictionaryWithObjectsAndKeys: + self.login, @"user[login]", + self.password, @"user[password]", nil]; +} + +@end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/elements b/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/elements index 49503f787d9774b9c07cfaf623be253a7e123901..93250fa5b2190368d662ede368f4c431a483510b 100644 GIT binary patch literal 37301 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 Oj0@d=jsLe@`TqddeWxe@ literal 29433 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 YE=w=VgslGvgx0_4vDUx#|I0N01+l{V0ssI2 diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/layout b/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/layout index 1efa47c469d32c09ad9116f52efc789e7a8ec73f..b43c2ce3bf9c2c86bac61fed3e586f8585e51dc1 100644 GIT binary patch literal 6988 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 diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj b/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj index 2233ea52..6ac012f4 100755 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/DiscussionBoard.xcodeproj/project.pbxproj @@ -12,6 +12,17 @@ 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; + 3F255A9712DB5F9200AFD2D1 /* DBManagedObjectCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255A9612DB5F9200AFD2D1 /* DBManagedObjectCache.m */; }; + 3F255C9812DB70E200AFD2D1 /* DBPostsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255C9312DB70E200AFD2D1 /* DBPostsTableViewController.m */; }; + 3F255C9912DB70E200AFD2D1 /* DBResourceListTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255C9512DB70E200AFD2D1 /* DBResourceListTableViewController.m */; }; + 3F255C9A12DB70E200AFD2D1 /* DBTopicsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255C9712DB70E200AFD2D1 /* DBTopicsTableViewController.m */; }; + 3F255CA012DB70E700AFD2D1 /* DBPost.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255C9D12DB70E700AFD2D1 /* DBPost.m */; }; + 3F255CA112DB70E700AFD2D1 /* DBTopic.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255C9F12DB70E700AFD2D1 /* DBTopic.m */; }; + 3F255CA812DB711900AFD2D1 /* DBUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255CA712DB711900AFD2D1 /* DBUser.m */; }; + 3F255CC612DB747C00AFD2D1 /* DBTopicViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255CC512DB747C00AFD2D1 /* DBTopicViewController.m */; }; + 3F255D1E12DB779B00AFD2D1 /* DBLoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255D1D12DB779B00AFD2D1 /* DBLoginViewController.m */; }; + 3F3239E112DBB9C600AB2F6E /* DBPostTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F3239E012DBB9C600AB2F6E /* DBPostTableViewController.m */; }; + 3F3239ED12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F3239EC12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m */; }; 3F45615B12D7E31100BE25AD /* libRestKitCoreData.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3B512D7822D00683D6F /* libRestKitCoreData.a */; }; 3F45615D12D7E31600BE25AD /* libRestKitJSONParserYAJL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3B112D7822D00683D6F /* libRestKitJSONParserYAJL.a */; }; 3F45615E12D7E31700BE25AD /* libRestKitNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3AB12D7822D00683D6F /* libRestKitNetwork.a */; }; @@ -24,20 +35,23 @@ 3F4562D012D7E50A00BE25AD /* libThree20Style-Xcode3.2.5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F45625A12D7E47000BE25AD /* libThree20Style-Xcode3.2.5.a */; }; 3F4562D112D7E50C00BE25AD /* libThree20UI-Xcode3.2.5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F45629012D7E47000BE25AD /* libThree20UI-Xcode3.2.5.a */; }; 3F4562D212D7E50F00BE25AD /* libThree20UICommon-Xcode3.2.5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F45626C12D7E47000BE25AD /* libThree20UICommon-Xcode3.2.5.a */; }; - 3F4563C212D7EA3500BE25AD /* DBPost.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F4563C112D7EA3500BE25AD /* DBPost.m */; }; 3F76BBD612D7DA9D001562DC /* DataModel.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = 3F76BBD512D7DA9D001562DC /* DataModel.xcdatamodel */; }; - 3F76BBF812D7DB5A001562DC /* DBTopicsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F76BBF712D7DB5A001562DC /* DBTopicsTableViewController.m */; }; - 3FB1FC3712D7ECDF00B2636A /* DBPostsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FB1FC3612D7ECDF00B2636A /* DBPostsTableViewController.m */; }; A7A2D33A12D780DD00683D6F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D33912D780DD00683D6F /* QuartzCore.framework */; }; A7A2D3C412D7829E00683D6F /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3C312D7829E00683D6F /* CFNetwork.framework */; }; A7A2D3C612D7829E00683D6F /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3C512D7829E00683D6F /* CoreData.framework */; }; A7A2D3C812D7829E00683D6F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3C712D7829E00683D6F /* MobileCoreServices.framework */; }; A7A2D3CA12D7829E00683D6F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7A2D3C912D7829E00683D6F /* SystemConfiguration.framework */; }; A7A2D47812D7849300683D6F /* DBEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = A7A2D47712D7849300683D6F /* DBEnvironment.m */; }; - A7A2D49F12D78DA500683D6F /* DBTopic.m in Sources */ = {isa = PBXBuildFile; fileRef = A7A2D49E12D78DA500683D6F /* DBTopic.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 3F2562F812DBA4C800AFD2D1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A7A2D39412D7822C00683D6F /* RestKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 2523360411E79F090048F9B4; + remoteInfo = RestKitThree20; + }; 3F45614B12D7E2C500BE25AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A7A2D39412D7822C00683D6F /* RestKit.xcodeproj */; @@ -462,6 +476,28 @@ 288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* DiscussionBoard_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiscussionBoard_Prefix.pch; sourceTree = ""; }; + 3F255A9512DB5F9200AFD2D1 /* DBManagedObjectCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBManagedObjectCache.h; sourceTree = ""; }; + 3F255A9612DB5F9200AFD2D1 /* DBManagedObjectCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBManagedObjectCache.m; sourceTree = ""; }; + 3F255C9212DB70E200AFD2D1 /* DBPostsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPostsTableViewController.h; sourceTree = ""; }; + 3F255C9312DB70E200AFD2D1 /* DBPostsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPostsTableViewController.m; sourceTree = ""; }; + 3F255C9412DB70E200AFD2D1 /* DBResourceListTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBResourceListTableViewController.h; sourceTree = ""; }; + 3F255C9512DB70E200AFD2D1 /* DBResourceListTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBResourceListTableViewController.m; sourceTree = ""; }; + 3F255C9612DB70E200AFD2D1 /* DBTopicsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBTopicsTableViewController.h; sourceTree = ""; }; + 3F255C9712DB70E200AFD2D1 /* DBTopicsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBTopicsTableViewController.m; sourceTree = ""; }; + 3F255C9C12DB70E700AFD2D1 /* DBPost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPost.h; sourceTree = ""; }; + 3F255C9D12DB70E700AFD2D1 /* DBPost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPost.m; sourceTree = ""; }; + 3F255C9E12DB70E700AFD2D1 /* DBTopic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBTopic.h; sourceTree = ""; }; + 3F255C9F12DB70E700AFD2D1 /* DBTopic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBTopic.m; sourceTree = ""; }; + 3F255CA612DB711900AFD2D1 /* DBUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBUser.h; sourceTree = ""; }; + 3F255CA712DB711900AFD2D1 /* DBUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBUser.m; sourceTree = ""; }; + 3F255CC412DB747C00AFD2D1 /* DBTopicViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBTopicViewController.h; sourceTree = ""; }; + 3F255CC512DB747C00AFD2D1 /* DBTopicViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBTopicViewController.m; sourceTree = ""; }; + 3F255D1C12DB779B00AFD2D1 /* DBLoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBLoginViewController.h; sourceTree = ""; }; + 3F255D1D12DB779B00AFD2D1 /* DBLoginViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBLoginViewController.m; sourceTree = ""; }; + 3F3239DF12DBB9C600AB2F6E /* DBPostTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPostTableViewController.h; sourceTree = ""; }; + 3F3239E012DBB9C600AB2F6E /* DBPostTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPostTableViewController.m; sourceTree = ""; }; + 3F3239EB12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBAuthenticatedTableViewController.h; sourceTree = ""; }; + 3F3239EC12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBAuthenticatedTableViewController.m; sourceTree = ""; }; 3F45621812D7E46F00BE25AD /* Three20Core.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Three20Core.xcodeproj; path = ../../../../three20/src/Three20Core/Three20Core.xcodeproj; sourceTree = SOURCE_ROOT; }; 3F45621B12D7E46F00BE25AD /* Three20Network.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Three20Network.xcodeproj; path = ../../../../three20/src/Three20Network/Three20Network.xcodeproj; sourceTree = SOURCE_ROOT; }; 3F45621E12D7E46F00BE25AD /* Three20Style.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Three20Style.xcodeproj; path = ../../../../three20/src/Three20Style/Three20Style.xcodeproj; sourceTree = SOURCE_ROOT; }; @@ -469,13 +505,7 @@ 3F45622412D7E46F00BE25AD /* Three20UINavigator.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Three20UINavigator.xcodeproj; path = ../../../../three20/src/Three20UINavigator/Three20UINavigator.xcodeproj; sourceTree = SOURCE_ROOT; }; 3F45622712D7E46F00BE25AD /* Three20UI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Three20UI.xcodeproj; path = ../../../../three20/src/Three20UI/Three20UI.xcodeproj; sourceTree = SOURCE_ROOT; }; 3F45622A12D7E46F00BE25AD /* Three20.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Three20.xcodeproj; path = ../../../../three20/src/Three20/Three20.xcodeproj; sourceTree = SOURCE_ROOT; }; - 3F4563C012D7EA3500BE25AD /* DBPost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPost.h; sourceTree = ""; }; - 3F4563C112D7EA3500BE25AD /* DBPost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPost.m; sourceTree = ""; }; 3F76BBD512D7DA9D001562DC /* DataModel.xcdatamodel */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = wrapper.xcdatamodel; path = DataModel.xcdatamodel; sourceTree = ""; }; - 3F76BBF612D7DB5A001562DC /* DBTopicsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBTopicsTableViewController.h; sourceTree = ""; }; - 3F76BBF712D7DB5A001562DC /* DBTopicsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBTopicsTableViewController.m; sourceTree = ""; }; - 3FB1FC3512D7ECDF00B2636A /* DBPostsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPostsTableViewController.h; sourceTree = ""; }; - 3FB1FC3612D7ECDF00B2636A /* DBPostsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPostsTableViewController.m; sourceTree = ""; }; 8D1107310486CEB800E47090 /* DiscussionBoard-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "DiscussionBoard-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; A7A2D33912D780DD00683D6F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; A7A2D39412D7822C00683D6F /* RestKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RestKit.xcodeproj; path = ../../../RestKit.xcodeproj; sourceTree = SOURCE_ROOT; }; @@ -485,8 +515,6 @@ A7A2D3C912D7829E00683D6F /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; A7A2D47612D7849300683D6F /* DBEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBEnvironment.h; sourceTree = ""; }; A7A2D47712D7849300683D6F /* DBEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBEnvironment.m; sourceTree = ""; }; - A7A2D49D12D78DA500683D6F /* DBTopic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBTopic.h; sourceTree = ""; }; - A7A2D49E12D78DA500683D6F /* DBTopic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBTopic.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -523,18 +551,14 @@ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( + 3F255C9B12DB70E700AFD2D1 /* Models */, + 3F255C9112DB70E200AFD2D1 /* Controllers */, 1D3623240D0F684500981E51 /* DiscussionBoardAppDelegate.h */, 1D3623250D0F684500981E51 /* DiscussionBoardAppDelegate.m */, A7A2D47612D7849300683D6F /* DBEnvironment.h */, A7A2D47712D7849300683D6F /* DBEnvironment.m */, - A7A2D49D12D78DA500683D6F /* DBTopic.h */, - A7A2D49E12D78DA500683D6F /* DBTopic.m */, - 3F76BBF612D7DB5A001562DC /* DBTopicsTableViewController.h */, - 3F76BBF712D7DB5A001562DC /* DBTopicsTableViewController.m */, - 3F4563C012D7EA3500BE25AD /* DBPost.h */, - 3F4563C112D7EA3500BE25AD /* DBPost.m */, - 3FB1FC3512D7ECDF00B2636A /* DBPostsTableViewController.h */, - 3FB1FC3612D7ECDF00B2636A /* DBPostsTableViewController.m */, + 3F255A9512DB5F9200AFD2D1 /* DBManagedObjectCache.h */, + 3F255A9612DB5F9200AFD2D1 /* DBManagedObjectCache.m */, ); path = Classes; sourceTree = ""; @@ -593,6 +617,40 @@ name = Frameworks; sourceTree = ""; }; + 3F255C9112DB70E200AFD2D1 /* Controllers */ = { + isa = PBXGroup; + children = ( + 3F255C9212DB70E200AFD2D1 /* DBPostsTableViewController.h */, + 3F255C9312DB70E200AFD2D1 /* DBPostsTableViewController.m */, + 3F255C9412DB70E200AFD2D1 /* DBResourceListTableViewController.h */, + 3F255C9512DB70E200AFD2D1 /* DBResourceListTableViewController.m */, + 3F255C9612DB70E200AFD2D1 /* DBTopicsTableViewController.h */, + 3F255C9712DB70E200AFD2D1 /* DBTopicsTableViewController.m */, + 3F255CC412DB747C00AFD2D1 /* DBTopicViewController.h */, + 3F255CC512DB747C00AFD2D1 /* DBTopicViewController.m */, + 3F255D1C12DB779B00AFD2D1 /* DBLoginViewController.h */, + 3F255D1D12DB779B00AFD2D1 /* DBLoginViewController.m */, + 3F3239DF12DBB9C600AB2F6E /* DBPostTableViewController.h */, + 3F3239E012DBB9C600AB2F6E /* DBPostTableViewController.m */, + 3F3239EB12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.h */, + 3F3239EC12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m */, + ); + path = Controllers; + sourceTree = ""; + }; + 3F255C9B12DB70E700AFD2D1 /* Models */ = { + isa = PBXGroup; + children = ( + 3F255C9C12DB70E700AFD2D1 /* DBPost.h */, + 3F255C9D12DB70E700AFD2D1 /* DBPost.m */, + 3F255C9E12DB70E700AFD2D1 /* DBTopic.h */, + 3F255C9F12DB70E700AFD2D1 /* DBTopic.m */, + 3F255CA612DB711900AFD2D1 /* DBUser.h */, + 3F255CA712DB711900AFD2D1 /* DBUser.m */, + ); + path = Models; + sourceTree = ""; + }; 3F45621912D7E46F00BE25AD /* Products */ = { isa = PBXGroup; children = ( @@ -730,6 +788,7 @@ dependencies = ( 3F45614C12D7E2C500BE25AD /* PBXTargetDependency */, 3F45615212D7E2D900BE25AD /* PBXTargetDependency */, + 3F2562F912DBA4C800AFD2D1 /* PBXTargetDependency */, 3F4562BD12D7E4D000BE25AD /* PBXTargetDependency */, 3F4562C112D7E4DE00BE25AD /* PBXTargetDependency */, 3F4562C312D7E4E200BE25AD /* PBXTargetDependency */, @@ -1172,17 +1231,29 @@ 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 1D3623260D0F684500981E51 /* DiscussionBoardAppDelegate.m in Sources */, A7A2D47812D7849300683D6F /* DBEnvironment.m in Sources */, - A7A2D49F12D78DA500683D6F /* DBTopic.m in Sources */, 3F76BBD612D7DA9D001562DC /* DataModel.xcdatamodel in Sources */, - 3F76BBF812D7DB5A001562DC /* DBTopicsTableViewController.m in Sources */, - 3F4563C212D7EA3500BE25AD /* DBPost.m in Sources */, - 3FB1FC3712D7ECDF00B2636A /* DBPostsTableViewController.m in Sources */, + 3F255A9712DB5F9200AFD2D1 /* DBManagedObjectCache.m in Sources */, + 3F255C9812DB70E200AFD2D1 /* DBPostsTableViewController.m in Sources */, + 3F255C9912DB70E200AFD2D1 /* DBResourceListTableViewController.m in Sources */, + 3F255C9A12DB70E200AFD2D1 /* DBTopicsTableViewController.m in Sources */, + 3F255CA012DB70E700AFD2D1 /* DBPost.m in Sources */, + 3F255CA112DB70E700AFD2D1 /* DBTopic.m in Sources */, + 3F255CA812DB711900AFD2D1 /* DBUser.m in Sources */, + 3F255CC612DB747C00AFD2D1 /* DBTopicViewController.m in Sources */, + 3F255D1E12DB779B00AFD2D1 /* DBLoginViewController.m in Sources */, + 3F3239E112DBB9C600AB2F6E /* DBPostTableViewController.m in Sources */, + 3F3239ED12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 3F2562F912DBA4C800AFD2D1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = RestKitThree20; + targetProxy = 3F2562F812DBA4C800AFD2D1 /* PBXContainerItemProxy */; + }; 3F45614C12D7E2C500BE25AD /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = RestKit; diff --git a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/application_controller.rb b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/application_controller.rb index cf767f18..81b4d82b 100644 --- a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/application_controller.rb +++ b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/application_controller.rb @@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base token = request.headers["HTTP_USER_ACCESS_TOKEN"] @user = User.find_by_single_access_token(token) if @user.nil? - render :json => {:error => "Invalid Access Token"} + render :json => {:error => "Invalid Access Token"}, :status => 401 end end diff --git a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/topics_controller.rb b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/topics_controller.rb index b69b0623..c1a1dabe 100644 --- a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/topics_controller.rb +++ b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/topics_controller.rb @@ -28,7 +28,7 @@ class TopicsController < ApplicationController def requre_owner unless @user == object.user - render :json => {:error => "Unauthorized"} + render :json => {:error => "Unauthorized"}, :status => 401 end end diff --git a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/users_controller.rb b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/users_controller.rb index 904fa518..5cb60c8f 100644 --- a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/users_controller.rb +++ b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/controllers/users_controller.rb @@ -3,21 +3,21 @@ class UsersController < ApplicationController def login # this method logs you in and returns you a single_access_token token for authentication. - user_session = UserSession.new(params) + user_session = UserSession.new(params[:user]) if user_session.save user = user_session.user - render :json => {:login => user.login, :single_access_token => user.single_access_token, :user_id => user.id} + render :json => {:login => user.login, :single_access_token => user.single_access_token, :id => user.id, :email => user.email} else - render :json => {:error => "Invalid Login"} + render :json => {:error => "Invalid Login"}, :status => 401 end end def signup user = User.new(params[:user]) if user.save - render :json => {:login => user.login, :single_access_token => user.single_access_token, :user_id => user.id} + render :json => {:login => user.login, :single_access_token => user.single_access_token, :id => user.id, :email => user.email} else - render :json => {:errors => user.errors.full_messages} + render :json => {:errors => user.errors.full_messages}, :status => 422 end end diff --git a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/topic.rb b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/topic.rb index 37f02fd1..96264c4f 100644 --- a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/topic.rb +++ b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/topic.rb @@ -1,4 +1,7 @@ class Topic < ActiveRecord::Base belongs_to :user has_many :posts + + validates_presence_of :name + validates_presence_of :user end diff --git a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/user.rb b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/user.rb index 88b5a99c..e9906940 100644 --- a/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/user.rb +++ b/Examples/RKDiscussionBoardExample/discussion_board_backend/app/models/user.rb @@ -1,4 +1,6 @@ class User < ActiveRecord::Base acts_as_authentic + validates_presence_of :login + validates_presence_of :email end