diff --git a/Code/Network/RKRequest.h b/Code/Network/RKRequest.h index e1f59257..2017436d 100644 --- a/Code/Network/RKRequest.h +++ b/Code/Network/RKRequest.h @@ -172,6 +172,11 @@ typedef enum RKRequestMethod { */ - (BOOL)isLoaded; +/** + * Returns YES when the request was sent to the specified resource path + */ +- (BOOL)wasSentToResourcePath:(NSString*)resourcePath; + @end /** diff --git a/Code/Network/RKRequest.m b/Code/Network/RKRequest.m index 6126e02b..d1adbc5e 100644 --- a/Code/Network/RKRequest.m +++ b/Code/Network/RKRequest.m @@ -272,4 +272,8 @@ return resourcePath; } +- (BOOL)wasSentToResourcePath:(NSString*)resourcePath { + return [[self resourcePath] isEqualToString:resourcePath]; +} + @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m index 63f06eae..3eec1e51 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBAuthenticatedTableViewController.m @@ -12,8 +12,8 @@ @implementation DBAuthenticatedTableViewController - (void)viewDidUnload { - [[NSNotificationCenter defaultCenter] removeObserver:self name:kUserLoggedInNotificationName object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kLoginCanceledNotificationName object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:DBUserDidLoginNotification object:nil]; + // [[NSNotificationCenter defaultCenter] removeObserver:self name:kLoginCanceledNotificationName object:nil]; } - (void)loadView { @@ -33,11 +33,15 @@ [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]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidLogin:) name:DBUserDidLoginNotification object:nil]; + // Register for login canceled notification. pop view controller. - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginCanceled:) name:kLoginCanceledNotificationName object:nil]; + // TODO: Not sure what's up with the cancel... + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginCanceled:) name:kLoginCanceledNotificationName object:nil]; + TTOpenURL(@"db://login"); } } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m index 0c817a28..0a6d5210 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBLoginViewController.m @@ -84,7 +84,8 @@ - (void)cancelButtonWasPressed:(id)sender { [self dismissModalViewControllerAnimated:YES]; - [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCanceledNotificationName object:self]; + // TODO: Do we need cancel login support??? +// [[NSNotificationCenter defaultCenter] postNotificationName:kLoginCanceledNotificationName object:self]; } - (void)signupOrLoginButtonItemWasPressed:(id)sender { @@ -103,7 +104,7 @@ if (_showingSignup) { // Signup DBUser* user = [DBUser object]; - user.login = _usernameField.text; + user.username = _usernameField.text; user.email = _emailField.text; user.password = _passwordField.text; user.passwordConfirmation = _passwordConfirmationField.text; @@ -112,7 +113,7 @@ } else { // Login DBUser* user = [DBUser object]; - user.login = _usernameField.text; + user.username = _usernameField.text; user.password = _passwordField.text; [[RKObjectManager sharedManager] putObject:user delegate:self]; } @@ -143,10 +144,6 @@ assert([objects count] == 1); DBUser* user = [objects objectAtIndex:0]; NSLog(@"Authentication Token: %@", user.singleAccessToken); - // TODO: Move this into the DBUser... - [[NSUserDefaults standardUserDefaults] setObject:user.userID forKey:kCurrentUserIDKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedInNotificationName object:user]; [self dismissModalViewControllerAnimated:YES]; } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m index 15116a18..c388d1a5 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBPostsTableViewController.m @@ -17,7 +17,7 @@ if (self = [super initWithStyle:UITableViewStylePlain]) { _topicID = [topicID retain]; self.title = @"Posts"; - // TODO: Use routing or something to generate this URL. RKGeneratePathWithObject + // TODO: Use routing or something to generate this URL. RKMakePathWithObject _resourcePath = [[NSString stringWithFormat:@"/topics/%@/posts", _topicID] retain]; _resourceClass = [DBPost class]; } @@ -43,7 +43,7 @@ - (void)addButtonWasPressed:(id)sender { // TODO: Move this onto the model... - // RKMakeObjectPath / RKGeneratePathWithObject / RKGeneratePathForObject(@"db://topics/(topicID)/posts/new", self.topic); + // RKMakeObjectPath / RKMakePathWithObject(@"db://topics/(topicID)/posts/new", self.topic); NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts/new", _topicID]; TTOpenURL(url); } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m index fa9f1023..ba77b376 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBResourceListTableViewController.m @@ -8,7 +8,9 @@ #import "DBResourceListTableViewController.h" #import "DBManagedObjectCache.h" +#import "DBUser.h" +// TODO: Move me somewhere else... @implementation UINavigationBar (CustomImage) - (void)drawRect:(CGRect)rect { @@ -22,6 +24,8 @@ - (void)loadView { [super loadView]; + + // TODO: Make this cleaner... UIImageView* backgroundImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background.png"]]; [self.navigationController.view addSubview:backgroundImage]; [self.navigationController.view sendSubviewToBack:backgroundImage]; @@ -47,10 +51,11 @@ UIView* tableSpacer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 31)]; self.tableView.tableHeaderView = tableSpacer; - // TODO: Use more generic notifications - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userStateChanged:) name:kUserLoggedInNotificationName object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userStateChanged:) name:kUserLoggedOutNotificationName object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadNotification:) name:kObjectCreatedUpdatedOrDestroyedNotificationName object:nil]; + // Register for notifications. We reload the interface when authentication state changes + // or the object graph is manipulated + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(invalidateModel) name:DBUserDidLoginNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(invalidateModel) name:DBUserDidLogoutNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(invalidateModel) name:kObjectCreatedUpdatedOrDestroyedNotificationName object:nil]; } - (void)reloadButtonWasPressed:(id)sender { @@ -64,15 +69,6 @@ [[NSNotificationCenter defaultCenter] removeObserver:self]; } -// TODO: Why not just use invalidateModel as the target of the notifications? -- (void)reloadNotification:(NSNotification*)note { - [self invalidateModel]; -} - -- (void)userStateChanged:(NSNotification*)note { - [self invalidateModel]; -} - // TODO: Why not use a createResourcePath method instead of an ivar? - (void)createModel { self.model = [[[RKRequestTTModel alloc] initWithResourcePath:_resourcePath] autorelease]; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m index 12c6d9b5..859cf25b 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Controllers/DBTopicsTableViewController.m @@ -27,7 +27,7 @@ [super createModel]; UIBarButtonItem* item = nil; - if ([DBUser currentUser]) { + if ([[DBUser currentUser] isLoggedIn]) { item = [[[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStyleBordered target:self action:@selector(logoutButtonWasPressed:)] autorelease]; } self.navigationItem.leftBarButtonItem = item; @@ -46,7 +46,7 @@ } - (void)logoutButtonWasPressed:(id)sender { - [DBUser logout]; + [[DBUser currentUser] logout]; } - (void)didLoadModel:(BOOL)firstTime { @@ -56,7 +56,7 @@ NSMutableArray* items = [NSMutableArray arrayWithCapacity:[model.objects count]]; for(DBTopic* topic in model.objects) { - // TODO: RKGeneratePathWithObject. Move to postsTTURL method? + // TODO: RKMakePathWithObject. Move to postsTTURL method? NSString* url = [NSString stringWithFormat:@"db://topics/%@/posts", topic.topicID]; [items addObject:[TTTableTextItem itemWithText:topic.name URL:url]]; } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h index d430a45c..894a2640 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.h @@ -8,6 +8,8 @@ #import #import +#import "DBUser.h" +#import "DBTopic.h" /** * The Post models an individual piece of content posted to @@ -38,6 +40,18 @@ //////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Relationship properties +/** + * The Topic that this Post belongs to. This is a Core Data relationship + * to the Topic object with the primary key value contained in the topicID property + */ +@property (nonatomic, retain) DBTopic* topic; + +/** + * The User who created this Post. This is a Core Data relationship + * to the User object with the primary key value contained in the userID property + */ +@property (nonatomic, retain) DBUser* user; + /** * The numeric primary key to the Topic this Post was made to * @@ -95,7 +109,4 @@ */ @property (nonatomic, retain) UIImage* newAttachment; -// TODO: Relationship to the User??? -// TODO: Relationship to the Topic??? - @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m index fcffe4b2..39d82f9d 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBPost.m @@ -6,8 +6,8 @@ // Copyright 2011 Two Toasters. All rights reserved. // -#import "DBPost.h" #import +#import "DBPost.h" @implementation DBPost @@ -23,6 +23,8 @@ @dynamic userID; @dynamic postID; @dynamic username; +@dynamic user; +@dynamic topic; @synthesize newAttachment = _newAttachment; @@ -49,15 +51,29 @@ /** * Informs RestKit which property contains the primary key for identifying - * this object. This is used to ensure that objects are updated + * this object. This is used to ensure that existing objects are updated during mapping */ + (NSString*)primaryKeyProperty { return @"postID"; } -// TODO: Fix encoding stuff with post[body] -// TODO: Fix bug. Can't edit stuff! -// TODO: paramsForGET / paramsForPOST / paramsForDELETE / paramsForPUT??? +/** + * Informs RestKit which properties contain the primary key values that + * can be used to hydrate relationships to other objects. This hint enables + * RestKit to automatically maintain true Core Data relationships between objects + * in your local store. + * + * Here we have asked RestKit to connect the 'user' relationship by performing a + * primary key lookup with the value in 'userID' property. This is the declarative + * equivalent of doing self.user = [DBUser objectWithPrimaryKeyValue:self.userID]; + */ ++ (NSDictionary*)relationshipToPrimaryKeyPropertyMappings { + return [NSDictionary dictionaryWithKeysAndObjects: + @"user", @"userID", + @"topic", @"topicID", + nil]; +} + /** * Return a serializable representation of this object's properties. This * serialization will be encoded by the router into a request body and @@ -69,13 +85,14 @@ * in your payloads. */ - (NSObject*)paramsForSerialization { - RKParams* params = [RKParams params]; - [params setValue:self.body forParam:@"post[body]"]; + // TODO: Should I expose the default super-class method as a dictionary returning method for clarity?? + NSDictionary* attributes = (NSDictionary*) [super paramsForSerialization]; + RKParams* params = [RKParams paramsWithDictionary:attributes]; NSLog(@"Self Body: %@", self.body); if (_newAttachment) { NSData* data = UIImagePNGRepresentation(_newAttachment); NSLog(@"Data Size: %d", [data length]); - RKParamsAttachment* attachment = [params setData:data MIMEType:@"application/octet-stream" forParam:@"post[attachment]"]; + RKParamsAttachment* attachment = [params setData:data MIMEType:@"application/octet-stream" forParam:@"attachment"]; attachment.fileName = @"image.png"; } diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h index 5ab2ad78..96276296 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.h @@ -8,30 +8,20 @@ #import #import +#import "DBUser.h" /** * Models a Topic in the Discussion Board. Users can * create Post's on Topics to have discussions. */ @interface DBTopic : RKManagedObject { - } -/** - * The numeric primary key for this topic in the remote backend system - */ -@property (nonatomic, retain) NSNumber* topicID; - /** * The name of this Topic. Identifies what we are discussing */ @property (nonatomic, retain) NSString* name; -/** - * The numeric primary key of the User who created this Topic - */ -@property (nonatomic, retain) NSNumber* userID; - /** * A timestamp of when the object was created */ @@ -47,6 +37,30 @@ */ @property (nonatomic, retain) NSString* username; -// TODO: Association with User +#pragma mark Relationship properties + +/** + * The User who created this Topic within the Discussion Board. + * This is a Core Data relationship to the User object with the + * primary key value contained in the userID property + */ +@property (nonatomic, retain) DBUser* user; + +/** + * The collection of Post objects that belong to this Topic. This is + * a Core Data relationship to the collection of Posts objects + * with a postID equal to the primary key (topicID) of this object. + */ +@property (nonatomic, retain) NSSet* posts; + +/** + * The numeric primary key for this topic in the remote backend system + */ +@property (nonatomic, retain) NSNumber* topicID; + +/** + * The numeric primary key of the User who created this Topic + */ +@property (nonatomic, retain) NSNumber* userID; @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m index 076678cf..bbc20f77 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBTopic.m @@ -16,6 +16,8 @@ @dynamic createdAt; @dynamic updatedAt; @dynamic username; +@dynamic user; +@dynamic posts; #pragma mark RKObjectMappable methods @@ -34,6 +36,22 @@ nil]; } +/** + * Informs RestKit which properties contain the primary key values that + * can be used to hydrate relationships to other objects. This hint enables + * RestKit to automatically maintain true Core Data relationships between objects + * in your local store. + * + * Here we have asked RestKit to connect the 'user' relationship by performing a + * primary key lookup with the value in 'userID' property. This is the declarative + * equivalent of doing self.user = [DBUser objectWithPrimaryKeyValue:self.userID]; + */ ++ (NSDictionary*)relationshipToPrimaryKeyPropertyMappings { + return [NSDictionary dictionaryWithKeysAndObjects: + @"user", @"userID", + nil]; +} + /** * Informs RestKit which property contains the primary key for identifying * this object. This is used to ensure that objects are updated @@ -42,11 +60,4 @@ return @"topicID"; } -// TODO: Eliminate this. Just use the Rails router -- (id)paramsForSerialization { - return [NSDictionary dictionaryWithObjectsAndKeys: - self.name, @"topic[name]", nil]; -} - - @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h index 368543ad..b8d557a9 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.h @@ -9,12 +9,23 @@ #import #import -@interface DBUser : RKManagedObject { +// Declared here and defined below +@protocol DBUserDelegate; + +//////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DBUser : RKManagedObject { // Transient. Used for login & sign-up NSString* _password; NSString* _passwordConfirmation; + NSObject* _delegate; } +/** + * The delegate for the User. Will be informed of session life-cycle events + */ +@property (nonatomic, retain) NSObject* delegate; + /** * The e-mail address of the User */ @@ -23,14 +34,11 @@ /** * The username of the User */ -// TODO: Inconsistencies between username & login -@property (nonatomic, retain) NSString* login; +@property (nonatomic, retain) NSString* username; -// Access Token will only be populated on a logged in user. /** * An Access Token returned when a User is authenticated */ -// TODO: Check design on this @property (nonatomic, retain) NSString* singleAccessToken; /** @@ -50,13 +58,60 @@ */ @property (nonatomic, retain) NSString* passwordConfirmation; +//////////////////////////////////////////////////////////////////////////////////////////////// + /** - * A globally available singleton reference to the current User + * A globally available singleton reference to the current User. When the User is + * not authenticated, a new object will be constructed and returned */ + (DBUser*)currentUser; -// TODO: Change to an instance method... -+ (void)logout; +/** + * Attempts to log a User into the system with a given username and password + */ +- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password; +/** + * Returns YES when the User is logged in + */ +- (BOOL)isLoggedIn; + +/** + * Logs the User out of the system + */ +- (void)logout; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * 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 + +/** + * Sent to the delegate when the User has successfully authenticated + */ +- (void)userDidLogin:(DBUser*)user; + +/** + * Sent to the delegate when the User failed login for a specific reason + */ +- (void)user:(DBUser*)user didFailLoginWithError:(NSError*)error; + +/** + * Sent to the delegate when the User logged out of the system + */ +- (void)userDidLogout:(DBUser*)user; @end diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m index 2b8c5c34..52046d27 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Models/DBUser.m @@ -8,59 +8,152 @@ #import "DBUser.h" +// Constants +static NSString* const kDBUserCurrentUserIDDefaultsKey = @"kDBUserCurrentUserIDDefaultsKey"; + +// Notifications +NSString* const DBUserDidLoginNotification = @"DBUserDidLoginNotification"; +NSString* const DBUserDidFailLoginNotification = @"DBUserDidFailLoginNotification"; +NSString* const DBUserDidLogoutNotification = @"DBUserDidLogoutNotification"; + @implementation DBUser @dynamic email; -@dynamic login; +@dynamic username; @dynamic singleAccessToken; @dynamic userID; - @synthesize password = _password; @synthesize passwordConfirmation = _passwordConfirmation; +@synthesize delegate = _delegate; +/** + * The property mapping dictionary. This method declares how elements in the JSON + * are mapped to properties on the object + */ + (NSDictionary*)elementToPropertyMappings { return [NSDictionary dictionaryWithKeysAndObjects: @"id", @"userID", @"email", @"email", - @"login", @"login", + @"username", @"login", @"single_access_token", @"singleAccessToken", @"password", @"password", @"password_confirmation", @"passwordConfirmation", nil]; } +/** + * Informs RestKit which property contains the primary key for identifying + * this object. This is used to ensure that existing objects are updated during mapping + */ + (NSString*)primaryKeyProperty { return @"userID"; } +/** + * Returns the singleton current User instance. There is always a User returned so that you + * are not sending messages to nil + */ + (DBUser*)currentUser { - id userID = [[NSUserDefaults standardUserDefaults] objectForKey:kCurrentUserIDKey]; - return [self objectWithPrimaryKeyValue:userID]; + id userID = [[NSUserDefaults standardUserDefaults] objectForKey:kDBUserCurrentUserIDDefaultsKey]; + if (userID) { + return [self objectWithPrimaryKeyValue:userID]; + } else { + return [self object]; + } } -+ (void)logout { - [[NSUserDefaults standardUserDefaults] setValue:nil forKey:kCurrentUserIDKey]; - [[NSUserDefaults standardUserDefaults] synchronize]; - [[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedOutNotificationName object:nil]; +/** + * 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 + * + */ +- (void)loginWithUsername:(NSString*)username andPassword:(NSString*)password { + RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] objectLoaderWithResourcePath:@"/login" delegate:self]; + objectLoader.method = RKRequestMethodPOST; + objectLoader.params = [NSDictionary dictionaryWithKeysAndObjects:@"username", username, @"password", password, nil]; + objectLoader.targetObject = self; + [objectLoader send]; } +/** + * Implementation of a RESTful logout pattern. We POST an object loader to + * the /logout resource path. This destroys the remote session + */ +- (void)logout { + RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] objectLoaderWithResourcePath:@"/logout" delegate:self]; + objectLoader.method = RKRequestMethodPOST; + objectLoader.targetObject = self; // TODO: Not sure I need this? + [objectLoader send]; +} + +- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects { + DBUser* user = [objects objectAtIndex:0]; + + 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]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:DBUserDidLoginNotification object:user]; + } else if ([objectLoader wasSentToResourcePath:@"/logout"]) { + // Logout was successful + + // Clear the stored credentials + [[NSUserDefaults standardUserDefaults] setValue:nil forKey:kDBUserCurrentUserIDDefaultsKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + // Inform the delegate + if ([self.delegate respondsToSelector:@selector(userDidLogout:)]) { + [self.delegate userDidLogout:self]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:DBUserDidLogoutNotification object:nil]; + } +} + +- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError*)error { + // Login failed + if ([objectLoader wasSentToResourcePath:@"/login"]) { + if ([self.delegate respondsToSelector:@selector(user:didFailLoginWithError:)]) { + [self.delegate user:self didFailLoginWithError:error]; + } + } +} + +// TODO: Do I need this? +- (void)reauthenticate { +} + +- (BOOL)isLoggedIn { + return self.singleAccessToken != nil; +} + +// TODO: Do I need this? +//- (NSObject*)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 { [_password release]; [_passwordConfirmation release]; [super dealloc]; } -- (NSObject*)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/Code/Other/DBEnvironment.h b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h index 06cc74cf..d4346114 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.h @@ -6,10 +6,6 @@ // Copyright 2011 Two Toasters. All rights reserved. // -/** - * General Constants - */ - /** * The Base URL constant. This Base URL is used to initialize RestKit via RKClient * or RKObjectManager (which in turn initializes an instance of RKClient). The Base @@ -19,15 +15,7 @@ * conditional compilation, you can very quickly switch between server environments * and produce builds targetted at different backend systems. */ -extern NSString* const kDBBaseURLString; - -// TODO: Gets moved to DBUser as an internal constant -extern NSString* const kCurrentUserIDKey; - -// TODO: Gets moved to DBUser -extern NSString* const kUserLoggedInNotificationName; -extern NSString* const kLoginCanceledNotificationName; -extern NSString* const kUserLoggedOutNotificationName; +extern NSString* const DBRestKitBaseURL; // TODO: See if we can eliminate or abstract this further extern NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.m index dc45fdb0..8c421ca8 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DBEnvironment.m @@ -8,12 +8,7 @@ #import "DBEnvironment.h" -//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 +// TODO: Add conditional compilation! +NSString* const DBRestKitBaseURL = @"http://localhost:3000"; +//NSString* const kDBBaseURLString = @"http://discussionboard.heroku.com"; +NSString* const kObjectCreatedUpdatedOrDestroyedNotificationName = @"kObjectCreatedUpdatedOrDestroyedNotificationName"; diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m index e85d0bad..2846198a 100644 --- a/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m +++ b/Examples/RKDiscussionBoardExample/DiscussionBoard/Code/Other/DiscussionBoardAppDelegate.m @@ -40,7 +40,7 @@ static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Initialize object manager - RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:kDBBaseURLString]; + RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:DBRestKitBaseURL]; // 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. @@ -96,8 +96,9 @@ static NSString* const kAccessTokenHeaderField = @"X-USER-ACCESS-TOKEN"; [[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]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userLoggedIn:) name:DBUserDidLoginNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userLoggedOut:) name:DBUserDidLogoutNotification object:nil]; + DBUser* user = [DBUser currentUser]; NSLog(@"Token: %@", user.singleAccessToken); NSLog(@"User: %@", user); diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/elements b/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/elements index 3d3ff91c..86bcceaa 100644 Binary files a/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/elements and b/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/elements differ diff --git a/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/layout b/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/layout index 7686034f..caf6975c 100644 Binary files a/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/layout and b/Examples/RKDiscussionBoardExample/DiscussionBoard/Resources/DataModel.xcdatamodel/layout differ