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 49503f78..93250fa5 100644 Binary files a/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/elements and b/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/elements differ diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/layout b/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/layout index 1efa47c4..b43c2ce3 100644 Binary files a/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/layout and b/Examples/RKDiscussionBoardExample/DiscussionBoard/DataModel.xcdatamodel/layout differ 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