Slide 1

Slide 1 text

Synchronizing Core Data With Rails Ken Collins metaskills.net Wednesday, July 14, 2010

Slide 2

Slide 2 text

Wednesday, July 14, 2010

Slide 3

Slide 3 text

Wednesday, July 14, 2010

Slide 4

Slide 4 text

Why Write Web Applications For The Web Only? Wednesday, July 14, 2010

Slide 5

Slide 5 text

Why Write Web Applications For The Web Only? Wednesday, July 14, 2010

Slide 6

Slide 6 text

Why Write Web Applications For The Web Only? THE BACK-END (server) Rails Web Application Wednesday, July 14, 2010

Slide 7

Slide 7 text

Why Write Web Applications For The Web Only? THE FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application Wednesday, July 14, 2010

Slide 8

Slide 8 text

Why Write Web Applications For The Web Only? THE FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application Wednesday, July 14, 2010

Slide 9

Slide 9 text

The "AJAX Head" Design Pattern Wednesday, July 14, 2010

Slide 10

Slide 10 text

The "AJAX Head" Design Pattern Wednesday, July 14, 2010

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

The "AJAX Head" Design Pattern http:/ /voodootikigod.com/ajax-head-design-pattern http:/ /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

Slide 13

Slide 13 text

Why Write Web Applications For The Web Only? THE FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application Wednesday, July 14, 2010

Slide 14

Slide 14 text

Why Write Web Applications For The Web Only? THE FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application 1) AR Model 2) Controller Wednesday, July 14, 2010

Slide 15

Slide 15 text

Why Write Web Applications For The Web Only? THE FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application 1) AR Model 2) Controller 1) CoreData Models 2) External API Wednesday, July 14, 2010

Slide 16

Slide 16 text

The Backend ( part 1 - web app model ) Wednesday, July 14, 2010

Slide 17

Slide 17 text

Wednesday, July 14, 2010

Slide 18

Slide 18 text

3.0.0.beta4 Wednesday, July 14, 2010

Slide 19

Slide 19 text

3.0.0.beta4 Wednesday, July 14, 2010

Slide 20

Slide 20 text

3.0.0.beta4 Wednesday, July 14, 2010

Slide 21

Slide 21 text

Data-Interchange... JSON Wednesday, July 14, 2010

Slide 22

Slide 22 text

Data-Interchange... JSON JSON (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

Slide 23

Slide 23 text

Data-Interchange... JSON JSON (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

Slide 24

Slide 24 text

JSON in Ruby/Rails class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values end end class Foo attr_accessor :bar end f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"} Wednesday, July 14, 2010

Slide 25

Slide 25 text

JSON in Ruby/Rails Specifically in ActiveSupport require 'active_support/json' class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values end end class Foo attr_accessor :bar end f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"} Wednesday, July 14, 2010

Slide 26

Slide 26 text

JSON in Ruby/Rails Specifically in ActiveSupport require '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 end end class Foo attr_accessor :bar end f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"} Wednesday, July 14, 2010

Slide 27

Slide 27 text

JSON in Ruby/Rails Specifically in ActiveSupport require '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 end end class Foo attr_accessor :bar end f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"} Wednesday, July 14, 2010

Slide 28

Slide 28 text

JSON in Ruby/Rails Specifically in ActiveSupport require '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 end end class Foo attr_accessor :bar end f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"} Wednesday, July 14, 2010

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

JSON in ActiveRecord 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 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

Slide 31

Slide 31 text

JSON in ActiveRecord class 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

Slide 32

Slide 32 text

JSON in ActiveRecord Don’t Over think! There Are Tools For Everything Use Them Wednesday, July 14, 2010

Slide 33

Slide 33 text

JSON in ActiveRecord data = "{\"position\": 1,\"name\":\"Prototype Framework\",\"url\": \"http://prototypejs.org/\", \"owner_id\":7181,\"id\": 27726,\"owner_type\":\"Box \"}" Bookmark.new.from_json(data) # => # Don’t Over think! There Are Tools For Everything Use Them Wednesday, July 14, 2010

Slide 34

Slide 34 text

The Backend ( part 2 - web app controller ) Wednesday, July 14, 2010

Slide 35

Slide 35 text

Changing State Wednesday, July 14, 2010

Slide 36

Slide 36 text

Changing State CREATE READ UPDATE DELETE DB HTTP INSERT SELECT UPDATE DELETE POST GET PUT DELETE Wednesday, July 14, 2010

Slide 37

Slide 37 text

Changing State CREATE READ UPDATE DELETE DB HTTP INSERT SELECT UPDATE DELETE POST GET PUT DELETE Wednesday, July 14, 2010

Slide 38

Slide 38 text

Changing State CREATE READ UPDATE DELETE DB HTTP INSERT SELECT UPDATE DELETE POST GET PUT DELETE Wednesday, July 14, 2010

Slide 39

Slide 39 text

Changing State CREATE READ UPDATE DELETE DB HTTP INSERT SELECT UPDATE DELETE POST GET PUT DELETE Representational State Transfer (REST) Wednesday, July 14, 2010

Slide 40

Slide 40 text

Resource Routes Wednesday, July 14, 2010

Slide 41

Slide 41 text

Resource Routes 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

Slide 42

Slide 42 text

Resource Routes 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

Slide 43

Slide 43 text

Resource Routes 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

Slide 44

Slide 44 text

Resource Routes 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

Slide 45

Slide 45 text

Resource Routes 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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Configure Mime Types Mime::Type.register_alias "text/html", :iphone Mime::Type.register_alias "text/html", :myiphoneapp_html Mime::Type.register_alias "application/json", :myiphoneapp_json Wednesday, July 14, 2010

Slide 49

Slide 49 text

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 Critical Support Wednesday, July 14, 2010

Slide 50

Slide 50 text

The Frontend ( part 1 - core data model ) Wednesday, July 14, 2010

Slide 51

Slide 51 text

ObjC RESTful Resource Wednesday, July 14, 2010

Slide 52

Slide 52 text

ObjC RESTful Resource Wednesday, July 14, 2010

Slide 53

Slide 53 text

ObjC RESTful Resource Wednesday, July 14, 2010

Slide 54

Slide 54 text

ObjC RESTful Resource Wednesday, July 14, 2010

Slide 55

Slide 55 text

ObjC RESTful Resource Wednesday, July 14, 2010

Slide 56

Slide 56 text

ObjC RESTful Resource http:/ /code.google.com/p/json-framework/ http:/ /allseeing-i.com/ASIHTTPRequest/ Wednesday, July 14, 2010

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

@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 Core Data Wednesday, July 14, 2010

Slide 59

Slide 59 text

#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

Slide 60

Slide 60 text

Taming Core Data Wednesday, July 14, 2010

Slide 61

Slide 61 text

Taming Core Data Set the class for all your models and generate classes! Wednesday, July 14, 2010

Slide 62

Slide 62 text

Taming Core Data Set the class for all your models and generate classes! Subclass NSManagedObject to your name space. Wednesday, July 14, 2010

Slide 63

Slide 63 text

Taming Core Data Set 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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

The Frontend ( part 2 - external api ) Wednesday, July 14, 2010

Slide 68

Slide 68 text

// 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 { Customize To Your API Wednesday, July 14, 2010

Slide 69

Slide 69 text

} #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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Import On Launch Wednesday, July 14, 2010

Slide 77

Slide 77 text

Import On Launch class 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

Slide 78

Slide 78 text

Import On Launch class 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

Slide 79

Slide 79 text

@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 Models Drive Wednesday, July 14, 2010

Slide 80

Slide 80 text

@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 Models Drive Wednesday, July 14, 2010

Slide 81

Slide 81 text

@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

Slide 82

Slide 82 text

@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

Slide 83

Slide 83 text

The End ( liked my talk – buy my app - write a review ) Wednesday, July 14, 2010