Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

    BACK-END (server) Rails Web Application Wednesday, July 14, 2010
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  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 1) AR Model 2) Controller 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 1) AR Model 2) Controller 1) CoreData Models 2) External API Wednesday, July 14, 2010
  9. The Backend ( part 1 - web app model )

    Wednesday, July 14, 2010
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. JSON in ActiveRecord Don’t Over think! There Are Tools For

    Everything Use Them Wednesday, July 14, 2010
  20. 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
  21. Changing State CREATE READ UPDATE DELETE DB HTTP INSERT SELECT

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

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

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

    UPDATE DELETE POST GET PUT DELETE Representational State Transfer (REST) Wednesday, July 14, 2010
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. @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
  35. #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
  36. Taming Core Data Set the class for all your models

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

    and generate classes! Subclass NSManagedObject to your name space. Wednesday, July 14, 2010
  38. 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
  39. 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
  40. 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
  41. 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
  42. // 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
  43. } #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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. @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
  53. @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
  54. @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
  55. @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
  56. The End ( liked my talk – buy my app

    - write a review ) Wednesday, July 14, 2010