Numerous cleanups to the DiscussionBoard example. Need to finish working through cleanups to the authentication process and polish off documentation. Should be ready for merge tomorrow night.

This commit is contained in:
Blake Watters
2011-01-18 23:25:19 -05:00
parent 7e2f49e814
commit 8f863b4c50
18 changed files with 305 additions and 114 deletions

View File

@@ -8,6 +8,8 @@
#import <Foundation/Foundation.h>
#import <RestKit/CoreData/CoreData.h>
#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

View File

@@ -6,8 +6,8 @@
// Copyright 2011 Two Toasters. All rights reserved.
//
#import "DBPost.h"
#import <RestKit/Support/NSDictionary+RKAdditions.h>
#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<RKRequestSerializable>*)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";
}

View File

@@ -8,30 +8,20 @@
#import <RestKit/RestKit.h>
#import <RestKit/CoreData/CoreData.h>
#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

View File

@@ -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<RKRequestSerializable>)paramsForSerialization {
return [NSDictionary dictionaryWithObjectsAndKeys:
self.name, @"topic[name]", nil];
}
@end

View File

@@ -9,12 +9,23 @@
#import <RestKit/RestKit.h>
#import <RestKit/CoreData/CoreData.h>
@interface DBUser : RKManagedObject {
// Declared here and defined below
@protocol DBUserDelegate;
////////////////////////////////////////////////////////////////////////////////////////////////
@interface DBUser : RKManagedObject <RKObjectLoaderDelegate> {
// Transient. Used for login & sign-up
NSString* _password;
NSString* _passwordConfirmation;
NSObject<DBUserDelegate>* _delegate;
}
/**
* The delegate for the User. Will be informed of session life-cycle events
*/
@property (nonatomic, retain) NSObject<DBUserDelegate>* 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

View File

@@ -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<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 {
[_password release];
[_passwordConfirmation release];
[super dealloc];
}
- (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];
}
return [NSDictionary dictionaryWithObjectsAndKeys:
self.login, @"user[login]",
self.password, @"user[password]", nil];
}
@end