Found a nasty bug in object mapping with nil values vs missing key paths. Polished login and sign up patterns thoroughly. Login/Logout & sign up are functional again. Now to move through the rest of the app

This commit is contained in:
Blake Watters
2011-01-20 00:14:21 -05:00
parent 723653585b
commit f7553d7ac0
16 changed files with 334 additions and 132 deletions

View File

@@ -1,5 +1,5 @@
//
// DBLoginViewController.h
// DBLoginOrSignUpViewController.h
// DiscussionBoard
//
// Created by Jeremy Ellison on 1/10/11.
@@ -9,8 +9,9 @@
#import <Three20/Three20.h>
#import <Three20/Three20+Additions.h>
#import <RestKit/RestKit.h>
#import "DBUser.h"
@interface DBLoginViewController : TTTableViewController <UITextFieldDelegate, RKObjectLoaderDelegate> {
@interface DBLoginOrSignUpViewController : TTTableViewController <UITextFieldDelegate, DBUserAuthenticationDelegate> {
UIBarButtonItem* _signupOrLoginButtonItem;
BOOL _showingSignup;

View File

@@ -1,15 +1,14 @@
//
// DBLoginViewController.m
// DBLoginOrSignUpViewController.m
// DiscussionBoard
//
// Created by Jeremy Ellison on 1/10/11.
// Copyright 2011 Two Toasters. All rights reserved.
//
#import "DBLoginViewController.h"
#import "DBUser.h"
#import "DBLoginOrSignUpViewController.h"
@implementation DBLoginViewController
@implementation DBLoginOrSignUpViewController
- (id)initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query {
if (self = [super initWithNavigatorURL:URL query:query]) {
@@ -21,6 +20,11 @@
}
- (void)viewDidUnload {
// Resign as the delegate
if ([DBUser currentUser].delegate == self) {
[DBUser currentUser].delegate = nil;
}
TT_RELEASE_SAFELY(_signupOrLoginButtonItem);
TT_RELEASE_SAFELY(_usernameField);
TT_RELEASE_SAFELY(_passwordField);
@@ -99,7 +103,6 @@
[self invalidateModel];
}
// TODO: Move login and sign-up into the model as higher level concepts
- (void)loginOrSignup {
if (_showingSignup) {
// Signup
@@ -108,14 +111,12 @@
user.email = _emailField.text;
user.password = _passwordField.text;
user.passwordConfirmation = _passwordConfirmationField.text;
[[RKObjectManager sharedManager] postObject:user delegate:self];
[user signUpWithDelegate:self];
} else {
// Login
DBUser* user = [DBUser object];
user.username = _usernameField.text;
user.password = _passwordField.text;
[[RKObjectManager sharedManager] putObject:user delegate:self];
DBUser* user = [DBUser object];
user.delegate = self;
[user loginWithUsername:_usernameField.text andPassword:_passwordField.text delegate:self];
}
}
@@ -140,15 +141,18 @@
return NO;
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
assert([objects count] == 1);
DBUser* user = [objects objectAtIndex:0];
NSLog(@"Authentication Token: %@", user.singleAccessToken);
#pragma mark DBUserAuthenticationDelegate methods
- (void)userDidLogin:(DBUser*)user {
[self dismissModalViewControllerAnimated:YES];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
// TODO: TTAlert???
- (void)user:(DBUser*)user didFailSignUpWithError:(NSError*)error {
TTAlert([error localizedDescription]);
}
- (void)user:(DBUser*)user didFailLoginWithError:(NSError*)error {
// TTAlert([error localizedDescription]);
[[[[UIAlertView alloc] initWithTitle:@"Error"
message:[error localizedDescription]
delegate:nil

View File

@@ -27,6 +27,7 @@
[super createModel];
UIBarButtonItem* item = nil;
NSLog(@"Updating model for Topics table! Current User is: %@", [DBUser currentUser]);
if ([[DBUser currentUser] isLoggedIn]) {
item = [[[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStyleBordered target:self action:@selector(logoutButtonWasPressed:)] autorelease];
}
@@ -46,6 +47,7 @@
}
- (void)logoutButtonWasPressed:(id)sender {
NSLog(@"Current user is: %@", [DBUser currentUser]);
[[DBUser currentUser] logout];
}

View File

@@ -10,7 +10,7 @@
#import <RestKit/CoreData/CoreData.h>
// Declared here and defined below
@protocol DBUserDelegate;
@protocol DBUserAuthenticationDelegate;
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -18,13 +18,13 @@
// Transient. Used for login & sign-up
NSString* _password;
NSString* _passwordConfirmation;
NSObject<DBUserDelegate>* _delegate;
NSObject<DBUserAuthenticationDelegate>* _delegate;
}
/**
* The delegate for the User. Will be informed of session life-cycle events
*/
@property (nonatomic, retain) NSObject<DBUserDelegate>* delegate;
@property (nonatomic, assign) NSObject<DBUserAuthenticationDelegate>* delegate;
/**
* The e-mail address of the User
@@ -66,10 +66,15 @@
*/
+ (DBUser*)currentUser;
/**
* Completes a sign up using the properties assigned to the object
*/
- (void)signUpWithDelegate:(NSObject<DBUserAuthenticationDelegate>*)delegate;
/**
* Attempts to log a User into the system with a given username and password
*/
- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password;
- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password delegate:(NSObject<DBUserAuthenticationDelegate>*)delegate;
/**
* Returns YES when the User is logged in
@@ -90,14 +95,26 @@
* Notifications
*/
extern NSString* const DBUserDidLoginNotification; // Posted when the User logs in
extern NSString* const DBUserDidFailLoginNotification; // Posted when the User fails login. userInfo contains NSError with reason
extern NSString* const DBUserDidLogoutNotification; // Posted when the User logs out
/**
* A protocol defining life-cycles events for a user logging in and out
* of the application
*/
@protocol DBUserDelegate
@protocol DBUserAuthenticationDelegate
@optional
/**
* Sent to the delegate when sign up has completed successfully. Immediately
* followed by an invocation of userDidLogin:
*/
- (void)userDidSignUp:(DBUser*)user;
/**
* Sent to the delegate when sign up failed for a specific reason
*/
- (void)user:(DBUser*)user didFailSignUpWithError:(NSError*)error;
/**
* Sent to the delegate when the User has successfully authenticated

View File

@@ -16,6 +16,9 @@ NSString* const DBUserDidLoginNotification = @"DBUserDidLoginNotification";
NSString* const DBUserDidFailLoginNotification = @"DBUserDidFailLoginNotification";
NSString* const DBUserDidLogoutNotification = @"DBUserDidLogoutNotification";
// Current User singleton
static DBUser* currentUser = nil;
@implementation DBUser
@dynamic email;
@@ -34,7 +37,7 @@ NSString* const DBUserDidLogoutNotification = @"DBUserDidLogoutNotification";
return [NSDictionary dictionaryWithKeysAndObjects:
@"id", @"userID",
@"email", @"email",
@"username", @"login",
@"username", @"username",
@"single_access_token", @"singleAccessToken",
@"password", @"password",
@"password_confirmation", @"passwordConfirmation",
@@ -54,24 +57,52 @@ NSString* const DBUserDidLogoutNotification = @"DBUserDidLogoutNotification";
* are not sending messages to nil
*/
+ (DBUser*)currentUser {
id userID = [[NSUserDefaults standardUserDefaults] objectForKey:kDBUserCurrentUserIDDefaultsKey];
if (userID) {
return [self objectWithPrimaryKeyValue:userID];
} else {
return [self object];
if (nil == currentUser) {
id userID = [[NSUserDefaults standardUserDefaults] objectForKey:kDBUserCurrentUserIDDefaultsKey];
if (userID) {
currentUser = [self objectWithPrimaryKeyValue:userID];
} else {
currentUser = [self object];
}
[currentUser retain];
}
return currentUser;
}
+ (void)setCurrentUser:(DBUser*)user {
[user retain];
[currentUser release];
currentUser = user;
}
/**
* Implementation of a RESTful sign-up pattern. We are just relying on RestKit for
* request/response processing and object mapping, but we have built a higher level
* abstraction around Sign-Up as a concept and exposed notifications and delegate
* methods that make it much more meaningful than a POST/parse/process cycle would be.
*/
- (void)signUpWithDelegate:(NSObject<DBUserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self];
}
/**
* Implementation of a RESTful login pattern. We construct an object loader addressed to
* the /login resource path and POST the credentials. The target of the object loader is
* set so that the login request
*
* set so that the login response gets mapped back into this object, populating the
* properties according to the mappings declared in elementToPropertyMappings.
*/
- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password {
- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password delegate:(NSObject<DBUserAuthenticationDelegate>*)delegate {
_delegate = delegate;
// TODO: Cleanup. Save the object store so that background threads can update this object
[[RKObjectManager sharedManager] saveObjectStore];
RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] objectLoaderWithResourcePath:@"/login" delegate:self];
objectLoader.method = RKRequestMethodPOST;
objectLoader.params = [NSDictionary dictionaryWithKeysAndObjects:@"username", username, @"password", password, nil];
objectLoader.params = [NSDictionary dictionaryWithKeysAndObjects:@"user[username]", username, @"user[password]", password, nil];
objectLoader.targetObject = self;
[objectLoader send];
}
@@ -80,32 +111,43 @@ NSString* const DBUserDidLogoutNotification = @"DBUserDidLogoutNotification";
* Implementation of a RESTful logout pattern. We POST an object loader to
* the /logout resource path. This destroys the remote session
*/
- (void)logout {
- (void)logout {
RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] objectLoaderWithResourcePath:@"/logout" delegate:self];
objectLoader.method = RKRequestMethodPOST;
objectLoader.targetObject = self; // TODO: Not sure I need this?
objectLoader.targetObject = self;
[objectLoader send];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
DBUser* user = [objects objectAtIndex:0];
- (void)loginWasSuccessful {
// Upon login, we become the current user
[DBUser setCurrentUser:self];
// Persist the UserID for recovery later
[[NSUserDefaults standardUserDefaults] setObject:self.userID forKey:kDBUserCurrentUserIDDefaultsKey];
[[NSUserDefaults standardUserDefaults] synchronize];
// Inform the delegate
if ([self.delegate respondsToSelector:@selector(userDidLogin:)]) {
[self.delegate userDidLogin:self];
}
[[NSNotificationCenter defaultCenter] postNotificationName:DBUserDidLoginNotification object:self];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
// NOTE: We don't need objects because self is the target of the mapping operation
if ([objectLoader wasSentToResourcePath:@"/login"]) {
// Login was successful
// Persist the UserID for recovery later
[[NSUserDefaults standardUserDefaults] setObject:user.userID forKey:kDBUserCurrentUserIDDefaultsKey];
[[NSUserDefaults standardUserDefaults] synchronize];
// Inform the delegate
if ([self.delegate respondsToSelector:@selector(userDidLogin:)]) {
[self.delegate userDidLogin:self];
[self loginWasSuccessful];
} else if ([objectLoader wasSentToResourcePath:@"/signup"]) {
// Sign Up was successful
if ([self.delegate respondsToSelector:@selector(userDidSignUp:)]) {
[self.delegate userDidSignUp:self];
}
RKObjectManager* objectManager = [RKObjectManager sharedManager];
[objectManager.client setValue:[DBUser currentUser].singleAccessToken forHTTPHeaderField:kAccessTokenHeaderField];
[[NSNotificationCenter defaultCenter] postNotificationName:DBUserDidLoginNotification object:user];
// Complete the login as well
[self loginWasSuccessful];
} else if ([objectLoader wasSentToResourcePath:@"/logout"]) {
// Logout was successful
@@ -118,46 +160,30 @@ NSString* const DBUserDidLogoutNotification = @"DBUserDidLogoutNotification";
[self.delegate userDidLogout:self];
}
RKObjectManager* objectManager = [RKObjectManager sharedManager];
[objectManager.client setValue:nil forHTTPHeaderField:kAccessTokenHeaderField];
[[NSNotificationCenter defaultCenter] postNotificationName:DBUserDidLogoutNotification object:nil];
}
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError*)error {
// Login failed
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError*)error {
if ([objectLoader wasSentToResourcePath:@"/login"]) {
// Login failed
if ([self.delegate respondsToSelector:@selector(user:didFailLoginWithError:)]) {
[self.delegate user:self didFailLoginWithError:error];
}
} else if ([objectLoader wasSentToResourcePath:@"/signup"]) {
// Sign Up failed
if ([self.delegate respondsToSelector:@selector(user:didFailSignUpWithError:)]) {
[self.delegate user:self didFailSignUpWithError:error];
}
}
}
// TODO: Do I need this?
- (void)reauthenticate {
}
- (BOOL)isLoggedIn {
return self.singleAccessToken != nil;
}
// TODO: Do I need this?
//- (NSObject<RKRequestSerializable>*)paramsForSerialization {
// if (_passwordConfirmation) {
// return [NSDictionary dictionaryWithObjectsAndKeys:
// self.email, @"user[email]",
// self.login, @"user[login]",
// self.password, @"user[password]",
// self.passwordConfirmation, @"user[password_confirmation]", nil];
// } else {
// return [NSDictionary dictionaryWithObjectsAndKeys:
// self.login, @"user[login]",
// self.password, @"user[password]", nil];
// }
//}
- (void)dealloc {
_delegate = nil;
[_password release];
[_passwordConfirmation release];
[super dealloc];

View File

@@ -20,4 +20,14 @@ extern NSString* const DBRestKitBaseURL;
// TODO: See if we can eliminate or abstract this further
extern NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName;
extern NSString* const kAccessTokenHeaderField;
/**
* Server Environments for conditional compilation
*/
#define DB_ENVIRONMENT_DEVELOPMENT 0
#define DB_ENVIRONMENT_STAGING 1
#define DB_ENVIRONMENT_PRODUCTION 2
// Use Production by default
#ifndef DB_ENVIRONMENT
#define DB_ENVIRONMENT DB_ENVIRONMENT_PRODUCTION
#endif

View File

@@ -8,9 +8,14 @@
#import "DBEnvironment.h"
// TODO: Add conditional compilation!
NSString* const DBRestKitBaseURL = @"http://localhost:3000";
//NSString* const kDBBaseURLString = @"http://discussionboard.heroku.com";
NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName = @"kObjectCreatedUpdatedOrDestroyedNotificationName";
// Base URL
#if DB_ENVIRONMENT == DB_ENVIRONMENT_DEVELOPMENT
NSString* const DBRestKitBaseURL = @"http://localhost:3000";
#elif DB_ENVIRONMENT == DB_ENVIRONMENT_STAGING
// TODO: Need a staging environment...
#elif DB_ENVIRONMENT == DB_ENVIRONMENT_PRODUCTION
NSString* const DBRestKitBaseURL = @"http://discussionboard.heroku.com";
#endif
NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN";
// TODO: Eliminate
NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName = @"kObjectCreatedUpdatedOrDestroyedNotificationName";

View File

@@ -24,17 +24,21 @@
#import "DBPost.h"
#import "DBManagedObjectCache.h"
#import "DBTopicViewController.h"
#import "DBLoginViewController.h"
#import "DBLoginOrSignUpViewController.h"
#import "DBUser.h"
#import "DBPostTableViewController.h"
/**
* The HTTP Header Field we transmit the authentication token obtained
* during login/sign-up back to the server. This token is verified server
* side to establish an authenticated session
*/
static NSString* const kDBAccessTokenHTTPHeaderField = @"X-USER-ACCESS-TOKEN";
@implementation DiscussionBoardAppDelegate
@synthesize window;
#pragma mark -
#pragma mark Application lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize object manager
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:DBRestKitBaseURL];
@@ -44,7 +48,9 @@
[RKRequestTTModel setDefaultRefreshRate:1];
// Do not overwrite properties that are missing in the payload to nil.
objectManager.mapper.missingElementMappingPolicy = RKIgnoreMissingElementMappingPolicy;
// TODO: Fix! There is a bug where elements that are in the payload
// objectManager.mapper.missingElementMappingPolicy = RKIgnoreMissingElementMappingPolicy;
objectManager.mapper.missingElementMappingPolicy = RKSetNilForMissingElementMappingPolicy;
// Initialize object store
objectManager.objectStore = [[[RKManagedObjectStore alloc] initWithStoreFilename:@"DiscussionBoard.sqlite"] autorelease];
@@ -56,18 +62,18 @@
[mapper registerClass:[DBPost class] forElementNamed:@"post"];
// Set Up Router
// TODO: Switch to Rails Router
RKDynamicRouter* router = [[[RKDynamicRouter alloc] init] autorelease];
// RKRailsRouter* router = [[[RKRailsRouter alloc] init] autorelease];
// TODO: Comment me!
RKRailsRouter* router = [[[RKRailsRouter alloc] init] autorelease];
[router setModelName:@"user" forClass:[DBUser class]];
[router routeClass:[DBUser class] toResourcePath:@"/signup" forMethod:RKRequestMethodPOST];
[router routeClass:[DBUser class] toResourcePath:@"/login" forMethod:RKRequestMethodPUT];
// [router setModelName:@"topic" forClass:[DBTopic class]];
[router setModelName:@"topic" forClass:[DBTopic class]];
[router routeClass:[DBTopic class] toResourcePath:@"/topics" forMethod:RKRequestMethodPOST];
[router routeClass:[DBTopic class] toResourcePath:@"/topics/(topicID)" forMethod:RKRequestMethodPUT];
[router routeClass:[DBTopic class] toResourcePath:@"/topics/(topicID)" forMethod:RKRequestMethodDELETE];
// [router setModelName:@"post" forClass:[DBPost class]];
[router setModelName:@"post" forClass:[DBPost class]];
[router routeClass:[DBPost class] toResourcePath:@"/topics/(topicID)/posts" forMethod:RKRequestMethodPOST];
[router routeClass:[DBPost class] toResourcePath:@"/topics/(topicID)/posts/(postID)" forMethod:RKRequestMethodPUT];
[router routeClass:[DBPost class] toResourcePath:@"/topics/(topicID)/posts/(postID)" forMethod:RKRequestMethodDELETE];
@@ -82,25 +88,39 @@
[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:@"db://login" toModalViewController:[DBLoginOrSignUpViewController class]];
[map from:@"*" toViewController:[TTWebController class]];
[[TTURLRequestQueue mainQueue] setMaxContentLength:0]; // Don't limit content length.
[[TTURLRequestQueue mainQueue] setMaxContentLength:0]; // Don't limit content length.
// Register for authentication notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setAccessTokenHeaderFromAuthenticationNotification:) name:DBUserDidLoginNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setAccessTokenHeaderFromAuthenticationNotification:) name:DBUserDidLogoutNotification object:nil];
// Initialize authenticated access if we have a logged in current User reference
DBUser* user = [DBUser currentUser];
if ([user isLoggedIn]) {
NSLog(@"Found logged in User record for username '%@' [Access Token: %@]", user.username, user.singleAccessToken);
[objectManager.client setValue:user.singleAccessToken forHTTPHeaderField:kDBAccessTokenHTTPHeaderField];
}
// Fire up the UI!
TTOpenURL(@"db://topics");
[[TTNavigator navigator].window makeKeyAndVisible];
DBUser* user = [DBUser currentUser];
NSLog(@"Token: %@", user.singleAccessToken);
NSLog(@"User: %@", user);
[objectManager.client setValue:[DBUser currentUser].singleAccessToken forHTTPHeaderField:kAccessTokenHeaderField];
return YES;
}
// Watch for login/logout events and set the Access Token HTTP Header
- (void)setAccessTokenHeaderFromAuthenticationNotification:(NSNotification*)notification {
DBUser* user = (DBUser*) [notification object];
RKObjectManager* objectManager = [RKObjectManager sharedManager];
[objectManager.client setValue:user.singleAccessToken forHTTPHeaderField:kDBAccessTokenHTTPHeaderField];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[window release];
[super dealloc];
}

View File

@@ -30,7 +30,7 @@
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 */; };
3F255D1E12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F255D1D12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.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 */; };
@@ -146,7 +146,7 @@
/* Begin PBXFileReference section */
1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1D6058910D05DD3D006BFB54 /* DiscussionBoard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DiscussionBoard.app; sourceTree = BUILT_PRODUCTS_DIR; };
1D6058910D05DD3D006BFB54 /* DiscussionBoard-Development.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DiscussionBoard-Development.app"; sourceTree = BUILT_PRODUCTS_DIR; };
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
25359D4212E3E0A4008BF33D /* libThree20.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libThree20.a; sourceTree = "<group>"; };
25359D4312E3E0A4008BF33D /* libThree20Core.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libThree20Core.a; sourceTree = "<group>"; };
@@ -435,6 +435,8 @@
25359ECF12E51070008BF33D /* DiscussionBoardAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DiscussionBoardAppDelegate.m; path = Code/Other/DiscussionBoardAppDelegate.m; sourceTree = SOURCE_ROOT; };
25359ED512E512BB008BF33D /* DiscussionBoard_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DiscussionBoard_Prefix.pch; path = Code/Other/DiscussionBoard_Prefix.pch; sourceTree = SOURCE_ROOT; };
25359ED612E512BB008BF33D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Code/Other/main.m; sourceTree = SOURCE_ROOT; };
25FED6E512E7D60600D94325 /* DiscussionBoard-Info copy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "DiscussionBoard-Info copy.plist"; path = "Resources/DiscussionBoard-Info copy.plist"; sourceTree = "<group>"; };
25FED7BC12E7D6EC00D94325 /* DiscussionBoard-Info copy 2.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "DiscussionBoard-Info copy 2.plist"; path = "Resources/DiscussionBoard-Info copy 2.plist"; sourceTree = "<group>"; };
288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
3F22448612DDFCB30002559D /* UIViewController+RKLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+RKLoading.h"; sourceTree = "<group>"; };
3F22448712DDFCB30002559D /* UIViewController+RKLoading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+RKLoading.m"; sourceTree = "<group>"; };
@@ -452,8 +454,8 @@
3F255CA712DB711900AFD2D1 /* DBUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBUser.m; sourceTree = "<group>"; };
3F255CC412DB747C00AFD2D1 /* DBTopicViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBTopicViewController.h; sourceTree = "<group>"; };
3F255CC512DB747C00AFD2D1 /* DBTopicViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBTopicViewController.m; sourceTree = "<group>"; };
3F255D1C12DB779B00AFD2D1 /* DBLoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBLoginViewController.h; sourceTree = "<group>"; };
3F255D1D12DB779B00AFD2D1 /* DBLoginViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBLoginViewController.m; sourceTree = "<group>"; };
3F255D1C12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBLoginOrSignUpViewController.h; sourceTree = "<group>"; };
3F255D1D12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBLoginOrSignUpViewController.m; sourceTree = "<group>"; };
3F3239DF12DBB9C600AB2F6E /* DBPostTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPostTableViewController.h; sourceTree = "<group>"; };
3F3239E012DBB9C600AB2F6E /* DBPostTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPostTableViewController.m; sourceTree = "<group>"; };
3F3239EB12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBAuthenticatedTableViewController.h; sourceTree = "<group>"; };
@@ -533,7 +535,7 @@
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
1D6058910D05DD3D006BFB54 /* DiscussionBoard.app */,
1D6058910D05DD3D006BFB54 /* DiscussionBoard-Development.app */,
);
name = Products;
sourceTree = "<group>";
@@ -954,6 +956,8 @@
A755037512E4CBD4009FC5EC /* PNGs */,
8D1107310486CEB800E47090 /* DiscussionBoard-Info.plist */,
3F76BBD512D7DA9D001562DC /* DataModel.xcdatamodel */,
25FED6E512E7D60600D94325 /* DiscussionBoard-Info copy.plist */,
25FED7BC12E7D6EC00D94325 /* DiscussionBoard-Info copy 2.plist */,
);
name = Resources;
sourceTree = "<group>";
@@ -984,8 +988,8 @@
3F255C9712DB70E200AFD2D1 /* DBTopicsTableViewController.m */,
3F255CC412DB747C00AFD2D1 /* DBTopicViewController.h */,
3F255CC512DB747C00AFD2D1 /* DBTopicViewController.m */,
3F255D1C12DB779B00AFD2D1 /* DBLoginViewController.h */,
3F255D1D12DB779B00AFD2D1 /* DBLoginViewController.m */,
3F255D1C12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.h */,
3F255D1D12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.m */,
3F3239DF12DBB9C600AB2F6E /* DBPostTableViewController.h */,
3F3239E012DBB9C600AB2F6E /* DBPostTableViewController.m */,
3F3239EB12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.h */,
@@ -1100,7 +1104,7 @@
);
name = DiscussionBoard;
productName = DiscussionBoard;
productReference = 1D6058910D05DD3D006BFB54 /* DiscussionBoard.app */;
productReference = 1D6058910D05DD3D006BFB54 /* DiscussionBoard-Development.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -1235,7 +1239,7 @@
3F255CA112DB70E700AFD2D1 /* DBTopic.m in Sources */,
3F255CA812DB711900AFD2D1 /* DBUser.m in Sources */,
3F255CC612DB747C00AFD2D1 /* DBTopicViewController.m in Sources */,
3F255D1E12DB779B00AFD2D1 /* DBLoginViewController.m in Sources */,
3F255D1E12DB779B00AFD2D1 /* DBLoginOrSignUpViewController.m in Sources */,
3F3239E112DBB9C600AB2F6E /* DBPostTableViewController.m in Sources */,
3F3239ED12DBBA8D00AB2F6E /* DBAuthenticatedTableViewController.m in Sources */,
3F22448812DDFCB30002559D /* UIViewController+RKLoading.m in Sources */,
@@ -1268,7 +1272,7 @@
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
1D6058940D05DD3E006BFB54 /* Debug */ = {
1D6058940D05DD3E006BFB54 /* Development */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
@@ -1277,7 +1281,10 @@
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Code/Other/DiscussionBoard_Prefix.pch;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_PREPROCESSOR_DEFINITIONS = (
"DB_ENVIRONMENT=0",
"DEBUG=1",
);
HEADER_SEARCH_PATHS = (
../../../Build,
Libraries/three20,
@@ -1289,18 +1296,21 @@
"\"$(SRCROOT)\"",
"\"$(SRCROOT)/Libraries/three20\"",
);
PRODUCT_NAME = DiscussionBoard;
PRODUCT_NAME = "DiscussionBoard-Development";
};
name = Debug;
name = Development;
};
1D6058950D05DD3E006BFB54 /* Release */ = {
1D6058950D05DD3E006BFB54 /* Production */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Code/Other/DiscussionBoard_Prefix.pch;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_PREPROCESSOR_DEFINITIONS = (
"DB_ENVIRONMENT=2",
"DEBUG=1",
);
HEADER_SEARCH_PATHS = (
../../../Build,
Libraries/three20,
@@ -1319,9 +1329,9 @@
PRODUCT_NAME = DiscussionBoard;
VALIDATE_PRODUCT = YES;
};
name = Release;
name = Production;
};
C01FCF4F08A954540054247B /* Debug */ = {
25FED7DA12E7D78E00D94325 /* Staging */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
@@ -1340,9 +1350,58 @@
PREBINDING = NO;
SDKROOT = iphoneos;
};
name = Debug;
name = Staging;
};
C01FCF5008A954540054247B /* Release */ = {
25FED7DB12E7D78E00D94325 /* Staging */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Code/Other/DiscussionBoard_Prefix.pch;
GCC_PREPROCESSOR_DEFINITIONS = (
"DB_ENVIRONMENT=1",
"DEBUG=1",
);
HEADER_SEARCH_PATHS = (
../../../Build,
Libraries/three20,
);
INFOPLIST_FILE = "Resources/DiscussionBoard-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 4.0;
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)\"",
"\"$(SRCROOT)/Libraries/three20\"",
);
PRODUCT_NAME = "DiscussionBoard-Staging";
};
name = Staging;
};
C01FCF4F08A954540054247B /* Development */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
GCC_C_LANGUAGE_STANDARD = c99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
../../../Build,
Libraries/three20/Build/Products/three20,
);
OTHER_LDFLAGS = (
"-all_load",
"-ObjC",
);
PREBINDING = NO;
SDKROOT = iphoneos;
};
name = Development;
};
C01FCF5008A954540054247B /* Production */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
@@ -1358,7 +1417,7 @@
PREBINDING = NO;
SDKROOT = iphoneos;
};
name = Release;
name = Production;
};
/* End XCBuildConfiguration section */
@@ -1366,20 +1425,22 @@
1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "DiscussionBoard" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1D6058940D05DD3E006BFB54 /* Debug */,
1D6058950D05DD3E006BFB54 /* Release */,
1D6058940D05DD3E006BFB54 /* Development */,
25FED7DB12E7D78E00D94325 /* Staging */,
1D6058950D05DD3E006BFB54 /* Production */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = Production;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "DiscussionBoard" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
C01FCF4F08A954540054247B /* Development */,
25FED7DA12E7D78E00D94325 /* Staging */,
C01FCF5008A954540054247B /* Production */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
defaultConfigurationName = Production;
};
/* End XCConfigurationList section */
};

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.twotoasters.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.twotoasters.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
</dict>
</plist>

View File

@@ -6,14 +6,14 @@ class SessionsController < ApplicationController
@user = @user_session.user
render :json => {:username => @user.username, :single_access_token => @user.single_access_token, :id => @user.id, :email => @user.email}
else
render :json => {:error => "Invalid username or password"}, :status => 401
render :json => {:errors => ["Invalid username or password"]}, :status => 401
end
end
def destroy
def destroy
if current_user
current_user.update_attributes!(:single_access_token => nil)
end
render :json => {}, :status => :ok
render :json => {:user => {:single_access_token => nil}}, :status => :ok
end
end

View File

@@ -4,4 +4,4 @@
# Examples:
#
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
# Mayor.create(:name => 'Daley', :city => cities.first)
# Mayor.create(:name => 'Daley', :city => cities.first)