$30 off During Our Annual Pro Sale. View Details »

Synchronizing Core Data With Rails

Synchronizing Core Data With Rails

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

Ken Collins

April 11, 2012
Tweet

More Decks by Ken Collins

Other Decks in Technology

Transcript

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

    14, 2010
  2. Wednesday, July 14, 2010

  3. Wednesday, July 14, 2010

  4. Why Write Web Applications For The Web Only? Wednesday, July

    14, 2010
  5. Why Write Web Applications For The Web <html> Only? Wednesday,

    July 14, 2010
  6. Why Write Web Applications For The Web <html> Only? THE

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

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

    FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application Wednesday, July 14, 2010
  9. The "AJAX Head" Design Pattern Wednesday, July 14, 2010

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

  11. 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
  12. 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
  13. Why Write Web Applications For The Web <html> Only? THE

    FRONT-END (clients) Browser - JavaScript iPhone - Objective-C THE BACK-END (server) Rails Web Application Wednesday, July 14, 2010
  14. Why Write Web Applications For The Web <html> 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
  15. Why Write Web Applications For The Web <html> 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
  16. The Backend ( part 1 - web app model )

    Wednesday, July 14, 2010
  17. Wednesday, July 14, 2010

  18. 3.0.0.beta4 Wednesday, July 14, 2010

  19. 3.0.0.beta4 Wednesday, July 14, 2010

  20. 3.0.0.beta4 Wednesday, July 14, 2010

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

  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. JSON in ActiveRecord Don’t Over think! There Are Tools For

    Everything Use Them Wednesday, July 14, 2010
  33. 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) # => #<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
  34. The Backend ( part 2 - web app controller )

    Wednesday, July 14, 2010
  35. Changing State Wednesday, July 14, 2010

  36. Changing State CREATE READ UPDATE DELETE DB HTTP INSERT SELECT

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

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

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

    UPDATE DELETE POST GET PUT DELETE Representational State Transfer (REST) Wednesday, July 14, 2010
  40. Resource Routes Wednesday, July 14, 2010

  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. The Frontend ( part 1 - core data model )

    Wednesday, July 14, 2010
  51. ObjC RESTful Resource Wednesday, July 14, 2010

  52. ObjC RESTful Resource Wednesday, July 14, 2010

  53. ObjC RESTful Resource Wednesday, July 14, 2010

  54. ObjC RESTful Resource Wednesday, July 14, 2010

  55. ObjC RESTful Resource Wednesday, July 14, 2010

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

    2010
  57. 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
  58. @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
  59. #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
  60. Taming Core Data Wednesday, July 14, 2010

  61. Taming Core Data Set the class for all your models

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

    and generate classes! Subclass NSManagedObject to your name space. Wednesday, July 14, 2010
  63. 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
  64. 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
  65. 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
  66. 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
  67. The Frontend ( part 2 - external api ) Wednesday,

    July 14, 2010
  68. // 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
  69. } #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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. Import On Launch Wednesday, July 14, 2010

  77. 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
  78. 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
  79. @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
  80. @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
  81. @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
  82. @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
  83. The End ( liked my talk – buy my app

    - write a review ) Wednesday, July 14, 2010