Upload
ken-collins
View
23.720
Download
1
Tags:
Embed Size (px)
DESCRIPTION
Lessons learned from building HomeMarks native iPhone application to synchronize Core Data with a RESTful backend built using rails 3.0.0.pre. This covers a previous design methodology called the AJAX head pattern which decouples rails applications from the views they present which allowed an easy API foundation for the iPhone application and data sync methods.
Citation preview
Synchronizing Core Data With Rails
Ken Collinsmetaskills.net
Wednesday, July 14, 2010
Wednesday, July 14, 2010
Wednesday, July 14, 2010
Why Write Web Applications For The
Web Only?
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
THE BACK-END (server)
Rails Web Application
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
THE FRONT-END (clients)
Browser - JavaScriptiPhone - Objective-C
THE BACK-END (server)
Rails Web Application
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
THE FRONT-END (clients)
Browser - JavaScriptiPhone - Objective-C
THE BACK-END (server)
Rails Web Application
Wednesday, July 14, 2010
The "AJAX Head" Design Pattern
Wednesday, July 14, 2010
The "AJAX Head" Design Pattern
Wednesday, July 14, 2010
The "AJAX Head" Design Pattern
“e AJAX head design pattern forces the view and controller to work in isolation with the most minimal
coupling possible. Kind of like a web service.”
“...an implementation pattern that drastically modi"es common web client-server interactions in
order to bring them more closely in line with enterprise client-server interactions...”
Wednesday, July 14, 2010
The "AJAX Head" Design Pattern
http://voodootikigod.com/ajax-head-design-patternhttp://metaskills.net/2008/5/24/the-ajax-head-br-design-pattern
http://metaskills.net/2008/6/18/restful-ajax-with-forgery-protection
“e AJAX head design pattern forces the view and controller to work in isolation with the most minimal
coupling possible. Kind of like a web service.”
“...an implementation pattern that drastically modi"es common web client-server interactions in
order to bring them more closely in line with enterprise client-server interactions...”
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
THE FRONT-END (clients)
Browser - JavaScriptiPhone - Objective-C
THE BACK-END (server)
Rails Web Application
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
THE FRONT-END (clients)
Browser - JavaScriptiPhone - Objective-C
THE BACK-END (server)
Rails Web Application
1) AR Model2) Controller
Wednesday, July 14, 2010
Why Write Web Applications For The Web <html> Only?
THE FRONT-END (clients)
Browser - JavaScriptiPhone - Objective-C
THE BACK-END (server)
Rails Web Application
1) AR Model2) Controller
1) CoreData Models2) External API
Wednesday, July 14, 2010
The Backend( part 1 - web app model )
Wednesday, July 14, 2010
Wednesday, July 14, 2010
3.0.0.beta4
Wednesday, July 14, 2010
3.0.0.beta4
Wednesday, July 14, 2010
3.0.0.beta4
Wednesday, July 14, 2010
Data-Interchange... JSON
Wednesday, July 14, 2010
Data-Interchange... JSONJSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and write. It is easy for machines to parse and generate. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family
of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.
Wednesday, July 14, 2010
Data-Interchange... JSONJSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and write. It is easy for machines to parse and generate. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family
of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.
http://www.json.org/
Wednesday, July 14, 2010
JSON in Ruby/Railsclass Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend
class Foo attr_accessor :barend
f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json' class Object
def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend
class Foo attr_accessor :barend
f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json'
Object#to_json calls #as_json to coerce itself into something natively encodable like Hash, Integer, or String. Override #as_json instead of #to_json so you're JSON library agnostic.
class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend
class Foo attr_accessor :barend
f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json'
Object#to_json calls #as_json to coerce itself into something natively encodable like Hash, Integer, or String. Override #as_json instead of #to_json so you're JSON library agnostic.
ActiveSupport::JSON does all the work of defining #as_json on all core ruby objects. It also abstracts out decoding with hot swappable backends using ActiveSupport::JSON.backend = 'JSONGem'.
class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend
class Foo attr_accessor :barend
f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json'
Object#to_json calls #as_json to coerce itself into something natively encodable like Hash, Integer, or String. Override #as_json instead of #to_json so you're JSON library agnostic.
ActiveSupport::JSON does all the work of defining #as_json on all core ruby objects. It also abstracts out decoding with hot swappable backends using ActiveSupport::JSON.backend = 'JSONGem'.
In rails, with ActiveSupport, primitive ruby objects serialize their instance variables using another core extension Object#instance_values which returns a Hash of said ivars.
class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend
class Foo attr_accessor :barend
f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
class Bookmark < ActiveRecord::Base JSON_ATTRS = ['id','owner_id','owner_type','url','name','position'] def as_json(options=nil) attributes.slice(*JSON_ATTRS) end end
JSON in ActiveRecord
Wednesday, July 14, 2010
JSON in ActiveRecordclass Bookmark < ActiveRecord::Base JSON_ATTRS = ['id','owner_id','owner_type','url','name','position'] def as_json(options=nil) attributes.slice(*JSON_ATTRS) end end
class Box < ActiveRecord::Base JSON_ATTRS = ['id','column_id','title','style','collapsed','position'] belongs_to :column has_many :bookmarks, :as => :owner, :order => 'position' acts_as_list :scope => :column_id def as_json(options=nil) attributes.slice(*JSON_ATTRS).merge(:bookmarks => bookmarks) end end
Wednesday, July 14, 2010
JSON in ActiveRecordclass User < ActiveRecord::Base JSON_ATTRS = ['id','email','uuid'].freeze has_one :inbox has_one :trashbox has_many :columns, :order => 'position' has_many :boxes, :through => :columns :order => 'columns.position, boxes.position' def as_json(options={}) attributes.slice(*JSON_ATTRS).merge( :inbox => inbox, :trashbox => trashbox, :columns => columns ) end end
>> Bookmark.find(27726).to_json=> "{\"position\":1,\"name\":\"Prototype Framework\",\"url\":\"http://prototypejs.org/\",\"owner_id\":7181,\"id\":27726,\"owner_type\":\"Box\"}"
>> Box.find(7181).to_json=> "{\"position\":4,\"title\":\"JavaScript Refs\",\"collapsed\":true,\"id\":7181,\"bookmarks\":[{\"position\":1,\"name\":\"Prototype Framework\",\"url\":\"http://prototypejs.org/\",\"owner_id\":7181,\"id\":27726,\"owner_type\":\"Box\"},{\"position\":2,\"name\":\"Scriptaclous Framework\",\"url\":\"http://script.aculo.us/\",\"owner_id\":7181,\"id\":27725,\"owner_type\":\"Box\"},{\"position\":3,\"name\":\"DevGuru (JavaScript)\",\"url\":\"http://www.devguru.com/technologies/javascript/home.asp\",\"owner_id\":7181,\"id\":27724,\"owner_type\":\"Box\"},{\"position\":4,\"name\":\"Dean Edwards Base.js\",\"url\":\"http://dean.edwards.name/weblog/2006/03/base/\",\"owner_id\":7181,\"id\":27723,\"owner_type\":\"Box\"}],\"column_id\":3538,\"style\":\"yellow_green\"}"
Wednesday, July 14, 2010
JSON in ActiveRecordDon’t Over think!
There Are Tools For Everything
Use Them
Wednesday, July 14, 2010
JSON in ActiveRecorddata = "{\"position\":1,\"name\":\"Prototype Framework\",\"url\":\"http://prototypejs.org/\",\"owner_id\":7181,\"id\":27726,\"owner_type\":\"Box\"}"
Bookmark.new.from_json(data)# => #<Bookmark id: nil, owner_id: nil, url: "http://prototypejs.org/", name: "Prototype Framework", created_at: nil, position: 0, owner_type: nil, updated_at: nil>
Don’t Over think!
There Are Tools For Everything
Use Them
Wednesday, July 14, 2010
The Backend( part 2 - web app controller )
Wednesday, July 14, 2010
Changing State
Wednesday, July 14, 2010
Changing State
CREATE READ UPDATE DELETE
DB
HTTP
INSERT SELECT UPDATE DELETE
POST GET PUT DELETE
Wednesday, July 14, 2010
Changing State
CREATE READ UPDATE DELETE
DB
HTTP
INSERT SELECT UPDATE DELETE
POST GET PUT DELETE
Wednesday, July 14, 2010
Changing State
CREATE READ UPDATE DELETE
DB
HTTP
INSERT SELECT UPDATE DELETE
POST GET PUT DELETE
Wednesday, July 14, 2010
Changing State
CREATE READ UPDATE DELETE
DB
HTTP
INSERT SELECT UPDATE DELETE
POST GET PUT DELETE
Representational State Transfer (REST)
Wednesday, July 14, 2010
ResourceRoutes
Wednesday, July 14, 2010
ResourceRoutes Homemarks::Application.routes.draw do |map|
resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end
Wednesday, July 14, 2010
ResourceRoutes
1 line 7 actions
Homemarks::Application.routes.draw do |map|
resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end
Wednesday, July 14, 2010
ResourceRoutes
1 line 7 actions
Collections & Members
Homemarks::Application.routes.draw do |map|
resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end
Wednesday, July 14, 2010
ResourceRoutes
1 line 7 actions
Collections & Members
Current User Scope Implied
Homemarks::Application.routes.draw do |map|
resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end
Wednesday, July 14, 2010
ResourceRoutes
1 line 7 actions
Collections & Members
Current User Scope Implied
You will PUT
Homemarks::Application.routes.draw do |map|
resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end
Wednesday, July 14, 2010
Generated URLs$ rake routes CONTROLLER=users
GET /users(.:format) {:controller=>"users", :action=>"index"} POST /users(.:format) {:controller=>"users", :action=>"create"} GET /users/new(.:format) {:controller=>"users", :action=>"new"} GET /users/:id(.:format) {:controller=>"users", :action=>"show"} PUT /users/:id(.:format) {:controller=>"users", :action=>"update"} DELETE /users/:id(.:format) {:controller=>"users", :action=>"destroy"} GET /users/:id/edit(.:format) {:controller=>"users", :action=>"edit"}
Wednesday, July 14, 2010
class BoxesController < ApplicationController def create @box = current_user.columns.find(params[:column_id]).boxes.create! render :json => @box.id end def sort @box.insert_at params[:position] ; head :ok end def toggle_collapse @box.toggle(:collapsed).save! ; head :ok end end
class SessionsController < ApplicationController
def create self.current_user = User.authenticate params[:email], params[:password] logged_in? ? head(:ok) : render(:json => login_failures, :status => :unauthorized) end
end
RESTful Actions
Wednesday, July 14, 2010
Configure Mime TypesMime::Type.register_alias "text/html", :iphoneMime::Type.register_alias "text/html", :myiphoneapp_htmlMime::Type.register_alias "application/json", :myiphoneapp_json
Wednesday, July 14, 2010
class ApplicationController < ActionController::Base protect_from_forgery before_filter :set_myiphone_app_request after_filter :brand_response_headers protected
def myiphone_app_request? request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/HomeMarks iPhone\/\d\.\d/] end def set_myiphone_app_request if myiphone_app_request? case request.format.symbol when :json then request.format = :myiphoneapp_json when :html then request.format = :myiphoneapp_html end end end
def brand_response_headers response.headers['X-Homemarks'] = '3.0' end
def set_iphone_request request.format = :iphone if iphone_request? end
def iphone_request? !myiphone_app_request? && request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(iPhone|iPod)/] end
def protect_against_forgery? myiphone_app_request? ? false : super end end
CriticalSupport
Wednesday, July 14, 2010
The Frontend( part 1 - core data model )
Wednesday, July 14, 2010
ObjC RESTful Resource
Wednesday, July 14, 2010
ObjC RESTful Resource
Wednesday, July 14, 2010
ObjC RESTful Resource
Wednesday, July 14, 2010
ObjC RESTful Resource
Wednesday, July 14, 2010
ObjC RESTful Resource
Wednesday, July 14, 2010
ObjC RESTful Resource
http://code.google.com/p/json-framework/http://allseeing-i.com/ASIHTTPRequest/
Wednesday, July 14, 2010
MyApp Singletons// Hard core singleton pattern.
[[UIApplication sharedApplication] openURL:...][[NSURLCredentialStorage sharedCredentialStorage] allCredentials]
// Simple class method singleton pattern.
[MyApp mom] // NSManagedObjectModel[MyApp moc] // NSManagedObjectContext[MyApp mocImport] // NSManagedObjectContext[MyApp psc] // NSPersistentStoreCoordinator
[MyApp userAgent] // NSString
[MyApp queue] // ASINetworkQueue[MyApp myUrlFor:action] // NSURL
Wednesday, July 14, 2010
@implementation MyApp
+ (NSManagedObjectModel *)mom { static NSManagedObjectModel *mom; if (mom == nil) { mom = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]; } return mom;}
+ (NSManagedObjectContext *)moc { static NSManagedObjectContext *moc; if (moc == nil) { NSPersistentStoreCoordinator *psc = [self psc]; if (psc != nil) { moc = [[NSManagedObjectContext alloc] init]; [moc setPersistentStoreCoordinator:psc]; [moc setUndoManager:nil]; [moc setStalenessInterval:3.0]; } } return moc;}
+ (NSManagedObjectContext *)mocImport { // Do same thing above, make sure undo mgr is nil.}
+ (NSPersistentStoreCoordinator *)psc { static NSPersistentStoreCoordinator *psc; if (psc == nil) // Normal PSC code here. return psc;}
@end
CoreData
Wednesday, July 14, 2010
#pragma mark Network
+ (ASINetworkQueue *)queue { static ASINetworkQueue *queue; if (queue == nil) { queue = [[ASINetworkQueue queue] retain]; [queue go]; } return queue;}
+ (NSURL *)myUrlFor:(NSString *)action { return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/%@", [self siteHost], action]];}
#pragma mark Device/Bundle
// http://www.drobnik.com/touch/2009/07/determining-the-hardware-model/
+ (NSString *)userAgent { static NSString *ua; if (ua == nil) { NSString *appVersion = [self appVersion]; NSString *platformString = [[UIDevice currentDevice] platformString]; NSString *systemVersion = [UIDevice currentDevice].systemVersion; ua = [[NSString stringWithFormat:@"HomeMarks iPhone/%@ (%@; %@)", appVersion,platformString,systemVersion] retain]; } return ua;}
Misc
Wednesday, July 14, 2010
Taming Core Data
Wednesday, July 14, 2010
Taming Core DataSet the class for all your models and generate classes!
Wednesday, July 14, 2010
Taming Core DataSet the class for all your models and generate classes!
Subclass NSManagedObject to your name space.
Wednesday, July 14, 2010
Taming Core DataSet the class for all your models and generate classes!
Subclass NSManagedObject to your name space.
Make all your generated model classes subclass from above.
Wednesday, July 14, 2010
Less Wizardry@implementation MyManagedObject
#pragma mark Reflection
+ (NSEntityDescription *)entity { return [[[MyApp mom] entitiesByName] objectForKey:NSStringFromClass(self)];}
+ (NSFetchRequest *)fetchRequestForEntity { NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease]; [fetchRequest setEntity:[self entity]]; return fetchRequest;}
@end
Wednesday, July 14, 2010
Less Wizardry@implementation MyManagedObject
#pragma mark Finders
+ (MyManagedObject *)objectWithID:(NSString *)anId { if (anId == nil) return nil; NSURL *myURL = [NSURL URLWithString:anId]; NSManagedObjectID *myId = [[MyApp psc] managedObjectIDForURIRepresentation:myURL]; return (MyManagedObject *)[[MyApp moc] objectWithID:myId];}
- (NSString *)objectIDURLString { return [[[self objectID] URIRepresentation] absoluteString];}
@end
x-coredata://EB8922D9- DC06-4256-A21B-DFFD47D7E6DA/MyEntity/p3
Wednesday, July 14, 2010
Create/Import Helpers@implementation MyManagedObject
#pragma mark Creators
+ (id)newObject:(NSDictionary *)attributes { return [self newObject:attributes inContext:nil];}
+ (id)newObject:(NSDictionary *)attributes inContext:(NSManagedObjectContext *)context { NSManagedObjectContext *moc = context ? context : [MyApp moc]; id mobj = [[self alloc] initWithEntity:[self entity] insertIntoManagedObjectContext:moc]; if (attributes != nil) { for (id key in attributes) { [mobj setValue:[attributes valueForKey:key] forKey:key]; } } return mobj;}
@end
Wednesday, July 14, 2010
The Frontend( part 2 - external api )
Wednesday, July 14, 2010
// Assumes MyRequest & MyFormRequest subclasses of // ASIHTTPRequest and ASIFormDataRequest (minimal use).
@implementation ASIHTTPRequest (MyAdditions)
#pragma mark Designated Initializer
- (id)initWithMyURL:(NSURL *)newURL { self = [self initWithURL:newURL]; self.username = [MyApp userEmail]; self.password = [MyApp userPass]; self.useCookiePersistance = NO; self.useSessionPersistance = NO; self.useKeychainPersistance = NO; self.allowCompressedResponse = YES; self.shouldRedirect = NO; self.requestMethod = @"GET"; self.delegate = [MyApp appDelegate]; self.didFinishSelector = @selector(globalRequestFinished:); self.didFailSelector = @selector(globalRequestFailed:); [self addRequestHeader:@"HTTP_ACCEPT" value:@"application/json"]; [self addRequestHeader:@"User-Agent" value:[MyApp userAgent]]; return self;}
#pragma mark Utility
+ (NSURL *)urlFor:(NSString *)action { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/%@",[MyApp siteHost],action]]; return url;}
+ (MyRequest *)myRequestFor:(NSString *)action { MyRequest *request = [[[MyRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; return request;}
+ (MyFormRequest *)myFormRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [[[MyFormRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; if (params != nil) { for (id key in params) { [request setPostValue:[params objectForKey:key] forKey:key]; } } request.requestMethod = @"POST"; return request;}
+ (MyFormRequest *)myPutRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [self myFormRequestFor:action withParams:params]; request.requestMethod = @"PUT"; return request;}
+ (MyRequest *)myDeleteRequestFor:(NSString *)action { MyRequest *request = [self myRequestFor:action]; request.requestMethod = @"DELETE"; return request;}
@end
Customize To Your API
Wednesday, July 14, 2010
// Assumes MyRequest & MyFormRequest subclasses of // ASIHTTPRequest and ASIFormDataRequest (minimal use).
@implementation ASIHTTPRequest (MyAdditions)
#pragma mark Designated Initializer
- (id)initWithMyURL:(NSURL *)newURL { self = [self initWithURL:newURL]; self.username = [MyApp userEmail]; self.password = [MyApp userPass]; self.useCookiePersistance = NO; self.useSessionPersistance = NO; self.useKeychainPersistance = NO; self.allowCompressedResponse = YES; self.shouldRedirect = NO; self.requestMethod = @"GET"; self.delegate = [MyApp appDelegate]; self.didFinishSelector = @selector(globalRequestFinished:); self.didFailSelector = @selector(globalRequestFailed:); [self addRequestHeader:@"HTTP_ACCEPT" value:@"application/json"]; [self addRequestHeader:@"User-Agent" value:[MyApp userAgent]]; return self;}
#pragma mark Utility
+ (NSURL *)urlFor:(NSString *)action { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/%@",[MyApp siteHost],action]]; return url;}
+ (MyRequest *)myRequestFor:(NSString *)action { MyRequest *request = [[[MyRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; return request;}
+ (MyFormRequest *)myFormRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [[[MyFormRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; if (params != nil) { for (id key in params) { [request setPostValue:[params objectForKey:key] forKey:key]; } } request.requestMethod = @"POST"; return request;}
+ (MyFormRequest *)myPutRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [self myFormRequestFor:action withParams:params]; request.requestMethod = @"PUT"; return request;}
+ (MyRequest *)myDeleteRequestFor:(NSString *)action { MyRequest *request = [self myRequestFor:action]; request.requestMethod = @"DELETE"; return request;}
@end
Wednesday, July 14, 2010
Import On Launch@implementation ASIHTTPRequest (MyAdditions)
+ (void)getUserDataFor:(id)aDelegate { MyRequest *request = [self myRequestFor:@"myhome.json"]; request.delegate = aDelegate; request.didFinishSelector = @selector(didGetUserData:); request.didFailSelector = @selector(didNotGetUserData:); if ([MyApp userEtag] != nil) { [request addRequestHeader:@"If-None-Match" value:[MyApp userEtag]]; } [[MyApp queue] addOperation:request];}
@end
Wednesday, July 14, 2010
Import On Launch@implementation ASIHTTPRequest (MyAdditions)
+ (void)getUserDataFor:(id)aDelegate { MyRequest *request = [self myRequestFor:@"myhome.json"]; request.delegate = aDelegate; request.didFinishSelector = @selector(didGetUserData:); request.didFailSelector = @selector(didNotGetUserData:); if ([MyApp userEtag] != nil) { [request addRequestHeader:@"If-None-Match" value:[MyApp userEtag]]; } [[MyApp queue] addOperation:request];}
@end
Wednesday, July 14, 2010
Import On Launch
- (void)didGetUserData:(MyRequest *)request { if ([request isNotModified]) { [[MyApp appDelegate] didGetUserData]; return; } else { [MyApp setUserEtag:[[request responseHeaders] objectForKey:@"Etag"]]; [self importUserData:[request responseJSON]]; [[MyApp appDelegate] didGetUserData]; }}
The Controller (request delegate)
Wednesday, July 14, 2010
Import On Launch
- (void)didGetUserData:(MyRequest *)request { if ([request isNotModified]) { [[MyApp appDelegate] didGetUserData]; return; } else { [MyApp setUserEtag:[[request responseHeaders] objectForKey:@"Etag"]]; [self importUserData:[request responseJSON]]; [[MyApp appDelegate] didGetUserData]; }}
The Controller (request delegate)
Wednesday, July 14, 2010
Import On Launch
- (void)didGetUserData:(MyRequest *)request { if ([request isNotModified]) { [[MyApp appDelegate] didGetUserData]; return; } else { [MyApp setUserEtag:[[request responseHeaders] objectForKey:@"Etag"]]; [self importUserData:[request responseJSON]]; [[MyApp appDelegate] didGetUserData]; }}
The Controller (request delegate)
"GET /myhome.json HTTP/1.1" 200 5115 "-" "MyApp iPhone/1.0 (iPhone 3GS; 3.1.3)""GET /myhome.json HTTP/1.1" 304 - "-" "MyApp iPhone/1.0 (iPhone 3GS; 3.1.3)"
Apache Logs
Wednesday, July 14, 2010
Import On Launch
- (void)didGetUserData:(MyRequest *)request { if ([request isNotModified]) { [[MyApp appDelegate] didGetUserData]; return; } else { [MyApp setUserEtag:[[request responseHeaders] objectForKey:@"Etag"]]; [self importUserData:[request responseJSON]]; [[MyApp appDelegate] didGetUserData]; }}
The Controller (request delegate)
"GET /myhome.json HTTP/1.1" 200 5115 "-" "MyApp iPhone/1.0 (iPhone 3GS; 3.1.3)""GET /myhome.json HTTP/1.1" 304 - "-" "MyApp iPhone/1.0 (iPhone 3GS; 3.1.3)"
Apache Logs
Wednesday, July 14, 2010
Import On Launch
Wednesday, July 14, 2010
Import On Launchclass UsersController < ApplicationController
def home if stale?(:etag => current_user) respond_to do |format| format.html { render :layout => 'application' } format.hmiphoneapp_json { render :json => current_user } end end end end
Wednesday, July 14, 2010
Import On Launchclass UsersController < ApplicationController
def home if stale?(:etag => current_user) respond_to do |format| format.html { render :layout => 'application' } format.hmiphoneapp_json { render :json => current_user } end end end end
@implementation MyAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearEtag:) name:NSManagedObjectContextDidSaveNotification object:[HMApp moc]]; //...}
- (void)clearEtag:(NSNotification *)notification { [MyApp removeUserEtag];}
@end
Wednesday, July 14, 2010
@implementation MyManagedObject
#pragma mark MyRequest Handling
- (void)createRemoteFinished:(MyRequest *)request { if ([request isSuccessfulMyAppText]) { NSInteger *remoteInt = [[request responseString] integerValue]; NSNumber *remoteId = [NSNumber numberWithInteger:remoteInt]; [self setValue:remoteId forKey:@"remoteId"]; [self save]; }}
@end
@implementation MyRequest
+ (void)columnCreate:(HMColumn *)column { MyFormRequest *request = [self myFormRequestFor:@"columns" withParams:nil]; request.delegate = column; request.didFinishSelector = @selector(createRemoteFinished:); request.didFailSelector = @selector(createRemoteFailed:); [[HMApp queue] addOperation:request];}
@end
Models Drive
Wednesday, July 14, 2010
@implementation MyManagedObject
#pragma mark MyRequest Handling
- (void)createRemoteFinished:(MyRequest *)request { if ([request isSuccessfulMyAppText]) { NSInteger *remoteInt = [[request responseString] integerValue]; NSNumber *remoteId = [NSNumber numberWithInteger:remoteInt]; [self setValue:remoteId forKey:@"remoteId"]; [self save]; }}
@end
@implementation HMColumn
+ (void)createRemote { HMColumn *newColumn = [[self class] newObject:nil]; //... [newColumn save]; [MyRequest columnCreate:newColumn]; [newColumn release];}
@end
@implementation MyRequest
+ (void)columnCreate:(HMColumn *)column { MyFormRequest *request = [self myFormRequestFor:@"columns" withParams:nil]; request.delegate = column; request.didFinishSelector = @selector(createRemoteFinished:); request.didFailSelector = @selector(createRemoteFailed:); [[HMApp queue] addOperation:request];}
@end
Models Drive
Wednesday, July 14, 2010
@implementation MyManagedObject
#pragma mark MyRequest Handling
- (void)createRemoteFinished:(MyRequest *)request { if ([request isSuccessfulMyAppText]) { NSInteger *remoteInt = [[request responseString] integerValue]; NSNumber *remoteId = [NSNumber numberWithInteger:remoteInt]; [self setValue:remoteId forKey:@"remoteId"]; [self save]; }}
@end
@implementation HMColumn
+ (void)createRemote { HMColumn *newColumn = [[self class] newObject:nil]; //... [newColumn save]; [MyRequest columnCreate:newColumn]; [newColumn release];}
@end
@implementation MyRequest
+ (void)columnCreate:(HMColumn *)column { MyFormRequest *request = [self myFormRequestFor:@"columns" withParams:nil]; request.delegate = column; request.didFinishSelector = @selector(createRemoteFinished:); request.didFailSelector = @selector(createRemoteFailed:); [[HMApp queue] addOperation:request];}
@end
Wednesday, July 14, 2010
@implementation HMBox
- (void)colorize { if ([[self changedValues] hasKey:@"style"] && [self save]) [HMRequest boxColorize:self];}
@end
@implementation ASIHTTPRequest (MyAdditions)
+ (NSString *)box:(HMBox *)box action:(NSString *)action { return [NSString stringWithFormat:@"boxes/%@/%@",[[box remoteId] stringValue],action];}
+ (void)boxColorize:(HMBox *)box { NSString *action = [self box:box action:@"colorize"]; NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:box.style,@"color",nil]; MyFormRequest *request = [self myPutRequestFor:action withParams:params]; [[MyApp queue] addOperation:request];}
@end
Models Drive (box example)
Wednesday, July 14, 2010
The End( liked my talk – buy my app - write a review )
Wednesday, July 14, 2010