Synchronizing Core Data With Rails
Upcoming SlideShare
Loading in...5
×
 

Synchronizing Core Data With Rails

on

  • 26,383 views

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 ...

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.

Statistics

Views

Total Views
26,383
Views on SlideShare
21,392
Embed Views
4,991

Actions

Likes
27
Downloads
487
Comments
0

13 Embeds 4,991

http://www.scoop.it 1929
http://metaskills.net 1434
http://www.metaskills.net 801
http://neokain.blogspot.com 583
http://happy-coding.com 106
http://www.slideshare.net 87
http://docketdev.mattmoriarity.com 33
http://metaskills.dev 6
http://webcache.googleusercontent.com 4
http://happy-coding.org 4
http://static.slidesharecdn.com 2
http://docketdev.posterous.com 1
http://notyce.furthere.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Synchronizing Core Data With Rails Synchronizing Core Data With Rails Presentation Transcript

  • Synchronizing Core Data With Rails Ken Collins metaskills.net
  • Real World Example
  • The "AJAX Head" Design Pattern
  • The "AJAX Head" Design Pattern
  • 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.”
  • 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.” http://metaskills.net/2008/5/24/the-ajax-head-br-design-pattern http://metaskills.net/2008/6/18/restful-ajax-with-forgery-protection http://metaskills.net/2008/8/18/in-hell-oo-for-homemarks
  • Domain Model
  • Domain Model Columns, Boxes, Bookmarks
  • Domain Model Columns, Boxes, Bookmarks Single Page Load
  • Domain Model Columns, Boxes, Bookmarks Single Page Load Everything Done Client-Side With JavaScript
  • Domain Model Columns, Boxes, Bookmarks Single Page Load Everything Done Client-Side With JavaScript Parallels With Atlas & Cappuccino
  • Domain Model Columns, Boxes, Bookmarks Single Page Load Everything Done Client-Side With JavaScript Parallels With http://cappuccino.org/ Atlas & Cappuccino http://280atlas.com/
  • The Backend ( part 1 - web app model )
  • 3.0.0.p re
  • Data-Interchange... JSON
  • 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.
  • 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/
  • 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"}
  • 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"}
  • JSON in Ruby/Rails Specifically in ActiveSupport require 'active_support/json' class Object def to_json(options = nil) Object#to_json calls #as_json to ActiveSupport::JSON.encode( coerce itself into something natively self,options) encodable like Hash, Integer, or String. end Override #as_json instead of #to_json def as_json(options = nil) so you're JSON library agnostic. instance_values end end class Foo attr_accessor :bar end f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"}
  • JSON in Ruby/Rails Specifically in ActiveSupport require 'active_support/json' class Object def to_json(options = nil) Object#to_json calls #as_json to ActiveSupport::JSON.encode( coerce itself into something natively self,options) encodable like Hash, Integer, or String. end Override #as_json instead of #to_json def as_json(options = nil) so you're JSON library agnostic. instance_values end ActiveSupport::JSON does all the work end of defining #as_json on all core ruby objects. It also abstracts out decoding class Foo with hot swappable backends using attr_accessor :bar ActiveSupport::JSON.backend = end JSONGem'. ' f = Foo.new f.bar = 'batz' puts f.to_json # => {"bar":"batz"}
  • JSON in Ruby/Rails Specifically in ActiveSupport require 'active_support/json' class Object def to_json(options = nil) Object#to_json calls #as_json to ActiveSupport::JSON.encode( coerce itself into something natively self,options) encodable like Hash, Integer, or String. end Override #as_json instead of #to_json def as_json(options = nil) so you're JSON library agnostic. instance_values end ActiveSupport::JSON does all the work end of defining #as_json on all core ruby objects. It also abstracts out decoding class Foo with hot swappable backends using attr_accessor :bar ActiveSupport::JSON.backend = end JSONGem'. ' f = Foo.new In rails, with ActiveSupport, primitive f.bar = 'batz' ruby objects serialize their instance puts f.to_json # => {"bar":"batz"} variables using another core extension Object#instance_values which returns a Hash of said ivars.
  • 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
  • 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
  • JSON in ActiveRecord >> Bookmark.find(27726).to_json => "{"position 1, ": "name "Prototype ": class User < ActiveRecord::Base Framework ", "url "http:/ ": /prototypejs.org/", "owner_id ":7181, ":27726, "id "owner_type": JSON_ATTRS = ['id','email','uuid'].freeze "Box "}" has_one :inbox >> Box.find(7181).to_json has_one :trashbox => "{ "position ":4, "title "JavaScript Refs ": ", has_many :columns, :order => 'position' "collapsed ":true, ":7181, "id "bookmarks ": has_many :boxes, :through => :columns [{ "position 1,": "name "Prototype Framework ": :order => 'columns.position, ", "url "http:/ ": /prototypejs.org/ ", "owner_id ": boxes.position' 7181, ":27726, "id "owner_type "Box ": "}, { "position ":2, "name "Scriptaclous ": Framework ", "url "http:/ ": /script.aculo.us/ ", def as_json(options={}) "owner_id ":7181, ":27725, "id "owner_type ": attributes.slice(*JSON_ATTRS).merge( "Box "position "},{ ":3, "name "DevGuru ": :inbox => inbox, (JavaScript) ", "url "http:/ ": / :trashbox => trashbox, www.devguru.com/technologies/javascript/ :columns => columns home.asp ", "owner_id ":7181, ": "id ) 27724, "owner_type "Box "position ": "},{ ": end 4, "name "Dean Edwards Base.js ": ", "url ": "http:/ /dean.edwards.name/weblog/ end 2006/03/base/ ", "owner_id ":7181, ": "id 27723, "owner_type "Box ": "}], "column_id ": 3538, "style "yellow_green ": "}"
  • JSON in ActiveRecord Don’t Over think! There Are Tools For Everything Use Them
  • JSON in ActiveRecord Don’t Over think! data = "{"position": 1,"name":"Prototype Framework","url": There Are Tools For "http://prototypejs.org/", "owner_id":7181,"id": Everything 27726,"owner_type":"Box "}" Use Them 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>
  • The Backend ( part 2 - web app controller )
  • Changing State
  • Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE
  • Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE
  • Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE
  • Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE Representational State Transfer (REST)
  • Resource Routes
  • 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
  • Resource Routes Homemarks::Application.routes.draw do |map| resources :users resource :session 1 line 7 actions 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
  • Resource Routes Homemarks::Application.routes.draw do |map| resources :users resource :session 1 line 7 actions resources :columns do collection { put :sort } member { delete :destroy_boxes } Collections & end Members resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end
  • Resource Routes Homemarks::Application.routes.draw do |map| resources :users resource :session 1 line 7 actions resources :columns do collection { put :sort } member { delete :destroy_boxes } Collections & end Members resources :boxes do collection { put :sort } member do Current User put :toggle_collapse put :colorize Scope Implied put :change_title end end end
  • Resource Routes Homemarks::Application.routes.draw do |map| resources :users resource :session 1 line 7 actions resources :columns do collection { put :sort } member { delete :destroy_boxes } Collections & end Members resources :boxes do collection { put :sort } member do Current User put :toggle_collapse put :colorize Scope Implied put :change_title end end You will PUT end
  • 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"}
  • RESTful Actions 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
  • 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
  • class ApplicationController < ActionController::Base Critical protect_from_forgery before_filter :set_myiphone_app_request after_filter :brand_response_headers protected Support def brand_response_headers response.headers['X-Homemarks'] = '3.0' end def myiphone_app_request? request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/HomeMarks iPhone/d.d/] end def iphone_request? !myiphone_app_request? && request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(iPhone|iPod)/] end def set_iphone_request request.format = :iphone if iphone_request? 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 protect_against_forgery? myiphone_app_request? ? false : super end end
  • The Frontend ( part 1 - core data model )
  • ObjC RESTful Resource
  • ObjC RESTful Resource
  • ObjC RESTful Resource
  • ObjC RESTful Resource
  • ObjC RESTful Resource
  • ObjC RESTful Resource http://code.google.com/p/json-framework/ http:/ /allseeing-i.com/ASIHTTPRequest/
  • 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
  • Core @implementation MyApp + (NSManagedObjectModel *)mom { static NSManagedObjectModel *mom; if (mom == nil) { Data 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
  • #pragma mark Network + (ASINetworkQueue *)queue { Misc 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; }
  • Taming Core Data
  • Taming Core Data Set the class for all your models and generate classes!
  • Taming Core Data Set the class for all your models and generate classes! Subclass NSManagedObject to your name space.
  • 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.
  • 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
  • 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
  • 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
  • The Frontend ( part 2 - external api )
  • Customize To Your API // Assumes MyRequest & MyFormRequest subclasses of // ASIHTTPRequest and ASIHTTPFormRequest (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; }
  • } #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
  • 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
  • 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
  • Import On Launch The Controller (request delegate) - (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]; } }
  • Import On Launch The Controller (request delegate) - (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]; } }
  • Import On Launch The Controller (request delegate) - (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]; } } Apache Logs "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)"
  • Import On Launch The Controller (request delegate) - (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]; } } Apache Logs "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)"
  • Import On Launch
  • 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
  • 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
  • Models Drive @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 @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 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 (box example) @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
  • The End ( liked my talk – buy my app - write a review )