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

Synchronizing Core Data With Rails

Embed Size (px)

DESCRIPTION

Lessons learned from building HomeMarks native iPhone application to synchronize Core Data with a RESTful backend built using rails 3.0.0.pre. This covers a previous design methodology called the AJAX head pattern which decouples rails applications from the views they present which allowed an easy API foundation for the iPhone application and data sync methods.

Citation preview

Page 1: Synchronizing Core Data With Rails

Synchronizing Core Data With Rails

Ken Collinsmetaskills.net

Wednesday, July 14, 2010

Page 2: Synchronizing Core Data With Rails

Wednesday, July 14, 2010

Page 3: Synchronizing Core Data With Rails

Wednesday, July 14, 2010

Page 4: Synchronizing Core Data With Rails

Why Write Web Applications For The

Web Only?

Wednesday, July 14, 2010

Page 5: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

Wednesday, July 14, 2010

Page 6: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

THE BACK-END (server)

Rails Web Application

Wednesday, July 14, 2010

Page 7: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

THE FRONT-END (clients)

Browser - JavaScriptiPhone - Objective-C

THE BACK-END (server)

Rails Web Application

Wednesday, July 14, 2010

Page 8: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

THE FRONT-END (clients)

Browser - JavaScriptiPhone - Objective-C

THE BACK-END (server)

Rails Web Application

Wednesday, July 14, 2010

Page 9: Synchronizing Core Data With Rails

The "AJAX Head" Design Pattern

Wednesday, July 14, 2010

Page 10: Synchronizing Core Data With Rails

The "AJAX Head" Design Pattern

Wednesday, July 14, 2010

Page 11: Synchronizing Core Data With Rails

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

Page 12: Synchronizing Core Data With Rails

The "AJAX Head" Design Pattern

http://voodootikigod.com/ajax-head-design-patternhttp://metaskills.net/2008/5/24/the-ajax-head-br-design-pattern

http://metaskills.net/2008/6/18/restful-ajax-with-forgery-protection

“e AJAX head design pattern forces the view and controller to work in isolation with the most minimal

coupling possible. Kind of like a web service.”

“...an implementation pattern that drastically modi"es common web client-server interactions in

order to bring them more closely in line with enterprise client-server interactions...”

Wednesday, July 14, 2010

Page 13: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

THE FRONT-END (clients)

Browser - JavaScriptiPhone - Objective-C

THE BACK-END (server)

Rails Web Application

Wednesday, July 14, 2010

Page 14: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

THE FRONT-END (clients)

Browser - JavaScriptiPhone - Objective-C

THE BACK-END (server)

Rails Web Application

1) AR Model2) Controller

Wednesday, July 14, 2010

Page 15: Synchronizing Core Data With Rails

Why Write Web Applications For The Web <html> Only?

THE FRONT-END (clients)

Browser - JavaScriptiPhone - Objective-C

THE BACK-END (server)

Rails Web Application

1) AR Model2) Controller

1) CoreData Models2) External API

Wednesday, July 14, 2010

Page 16: Synchronizing Core Data With Rails

The Backend( part 1 - web app model )

Wednesday, July 14, 2010

Page 17: Synchronizing Core Data With Rails

Wednesday, July 14, 2010

Page 18: Synchronizing Core Data With Rails

3.0.0.beta4

Wednesday, July 14, 2010

Page 19: Synchronizing Core Data With Rails

3.0.0.beta4

Wednesday, July 14, 2010

Page 20: Synchronizing Core Data With Rails

3.0.0.beta4

Wednesday, July 14, 2010

Page 21: Synchronizing Core Data With Rails

Data-Interchange... JSON

Wednesday, July 14, 2010

Page 22: Synchronizing Core Data With Rails

Data-Interchange... JSONJSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and write. It is easy for machines to parse and generate. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family

of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.

Wednesday, July 14, 2010

Page 23: Synchronizing Core Data With Rails

Data-Interchange... JSONJSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and write. It is easy for machines to parse and generate. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family

of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.

http://www.json.org/

Wednesday, July 14, 2010

Page 24: Synchronizing Core Data With Rails

JSON in Ruby/Railsclass Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend

class Foo attr_accessor :barend

f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}

Wednesday, July 14, 2010

Page 25: Synchronizing Core Data With Rails

JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json' class Object

def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend

class Foo attr_accessor :barend

f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}

Wednesday, July 14, 2010

Page 26: Synchronizing Core Data With Rails

JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json'

Object#to_json calls #as_json to coerce itself into something natively encodable like Hash, Integer, or String. Override #as_json instead of #to_json so you're JSON library agnostic.

class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend

class Foo attr_accessor :barend

f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}

Wednesday, July 14, 2010

Page 27: Synchronizing Core Data With Rails

JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json'

Object#to_json calls #as_json to coerce itself into something natively encodable like Hash, Integer, or String. Override #as_json instead of #to_json so you're JSON library agnostic.

ActiveSupport::JSON does all the work of defining #as_json on all core ruby objects. It also abstracts out decoding with hot swappable backends using ActiveSupport::JSON.backend = 'JSONGem'.

class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend

class Foo attr_accessor :barend

f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}

Wednesday, July 14, 2010

Page 28: Synchronizing Core Data With Rails

JSON in Ruby/RailsSpecifically in ActiveSupportrequire 'active_support/json'

Object#to_json calls #as_json to coerce itself into something natively encodable like Hash, Integer, or String. Override #as_json instead of #to_json so you're JSON library agnostic.

ActiveSupport::JSON does all the work of defining #as_json on all core ruby objects. It also abstracts out decoding with hot swappable backends using ActiveSupport::JSON.backend = 'JSONGem'.

In rails, with ActiveSupport, primitive ruby objects serialize their instance variables using another core extension Object#instance_values which returns a Hash of said ivars.

class Object def to_json(options = nil) ActiveSupport::JSON.encode( self,options) end def as_json(options = nil) instance_values endend

class Foo attr_accessor :barend

f = Foo.newf.bar = 'batz'puts f.to_json # => {"bar":"batz"}

Wednesday, July 14, 2010

Page 29: Synchronizing Core Data With Rails

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

Page 30: Synchronizing Core Data With Rails

JSON in ActiveRecordclass Bookmark < ActiveRecord::Base JSON_ATTRS = ['id','owner_id','owner_type','url','name','position'] def as_json(options=nil) attributes.slice(*JSON_ATTRS) end end

class Box < ActiveRecord::Base JSON_ATTRS = ['id','column_id','title','style','collapsed','position'] belongs_to :column has_many :bookmarks, :as => :owner, :order => 'position' acts_as_list :scope => :column_id def as_json(options=nil) attributes.slice(*JSON_ATTRS).merge(:bookmarks => bookmarks) end end

Wednesday, July 14, 2010

Page 31: Synchronizing Core Data With Rails

JSON in ActiveRecordclass User < ActiveRecord::Base JSON_ATTRS = ['id','email','uuid'].freeze has_one :inbox has_one :trashbox has_many :columns, :order => 'position' has_many :boxes, :through => :columns :order => 'columns.position, boxes.position' def as_json(options={}) attributes.slice(*JSON_ATTRS).merge( :inbox => inbox, :trashbox => trashbox, :columns => columns ) end end

>> Bookmark.find(27726).to_json=> "{\"position\":1,\"name\":\"Prototype Framework\",\"url\":\"http://prototypejs.org/\",\"owner_id\":7181,\"id\":27726,\"owner_type\":\"Box\"}"

>> Box.find(7181).to_json=> "{\"position\":4,\"title\":\"JavaScript Refs\",\"collapsed\":true,\"id\":7181,\"bookmarks\":[{\"position\":1,\"name\":\"Prototype Framework\",\"url\":\"http://prototypejs.org/\",\"owner_id\":7181,\"id\":27726,\"owner_type\":\"Box\"},{\"position\":2,\"name\":\"Scriptaclous Framework\",\"url\":\"http://script.aculo.us/\",\"owner_id\":7181,\"id\":27725,\"owner_type\":\"Box\"},{\"position\":3,\"name\":\"DevGuru (JavaScript)\",\"url\":\"http://www.devguru.com/technologies/javascript/home.asp\",\"owner_id\":7181,\"id\":27724,\"owner_type\":\"Box\"},{\"position\":4,\"name\":\"Dean Edwards Base.js\",\"url\":\"http://dean.edwards.name/weblog/2006/03/base/\",\"owner_id\":7181,\"id\":27723,\"owner_type\":\"Box\"}],\"column_id\":3538,\"style\":\"yellow_green\"}"

Wednesday, July 14, 2010

Page 32: Synchronizing Core Data With Rails

JSON in ActiveRecordDon’t Over think!

There Are Tools For Everything

Use Them

Wednesday, July 14, 2010

Page 33: Synchronizing Core Data With Rails

JSON in ActiveRecorddata = "{\"position\":1,\"name\":\"Prototype Framework\",\"url\":\"http://prototypejs.org/\",\"owner_id\":7181,\"id\":27726,\"owner_type\":\"Box\"}"

Bookmark.new.from_json(data)# => #<Bookmark id: nil, owner_id: nil, url: "http://prototypejs.org/", name: "Prototype Framework", created_at: nil, position: 0, owner_type: nil, updated_at: nil>

Don’t Over think!

There Are Tools For Everything

Use Them

Wednesday, July 14, 2010

Page 34: Synchronizing Core Data With Rails

The Backend( part 2 - web app controller )

Wednesday, July 14, 2010

Page 35: Synchronizing Core Data With Rails

Changing State

Wednesday, July 14, 2010

Page 36: Synchronizing Core Data With Rails

Changing State

CREATE READ UPDATE DELETE

DB

HTTP

INSERT SELECT UPDATE DELETE

POST GET PUT DELETE

Wednesday, July 14, 2010

Page 37: Synchronizing Core Data With Rails

Changing State

CREATE READ UPDATE DELETE

DB

HTTP

INSERT SELECT UPDATE DELETE

POST GET PUT DELETE

Wednesday, July 14, 2010

Page 38: Synchronizing Core Data With Rails

Changing State

CREATE READ UPDATE DELETE

DB

HTTP

INSERT SELECT UPDATE DELETE

POST GET PUT DELETE

Wednesday, July 14, 2010

Page 39: Synchronizing Core Data With Rails

Changing State

CREATE READ UPDATE DELETE

DB

HTTP

INSERT SELECT UPDATE DELETE

POST GET PUT DELETE

Representational State Transfer (REST)

Wednesday, July 14, 2010

Page 40: Synchronizing Core Data With Rails

ResourceRoutes

Wednesday, July 14, 2010

Page 41: Synchronizing Core Data With Rails

ResourceRoutes Homemarks::Application.routes.draw do |map|

resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end

Wednesday, July 14, 2010

Page 42: Synchronizing Core Data With Rails

ResourceRoutes

1 line 7 actions

Homemarks::Application.routes.draw do |map|

resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end

Wednesday, July 14, 2010

Page 43: Synchronizing Core Data With Rails

ResourceRoutes

1 line 7 actions

Collections & Members

Homemarks::Application.routes.draw do |map|

resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end

Wednesday, July 14, 2010

Page 44: Synchronizing Core Data With Rails

ResourceRoutes

1 line 7 actions

Collections & Members

Current User Scope Implied

Homemarks::Application.routes.draw do |map|

resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end

Wednesday, July 14, 2010

Page 45: Synchronizing Core Data With Rails

ResourceRoutes

1 line 7 actions

Collections & Members

Current User Scope Implied

You will PUT

Homemarks::Application.routes.draw do |map|

resources :users resource :session resources :columns do collection { put :sort } member { delete :destroy_boxes } end resources :boxes do collection { put :sort } member do put :toggle_collapse put :colorize put :change_title end end end

Wednesday, July 14, 2010

Page 46: Synchronizing Core Data With Rails

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

Page 47: Synchronizing Core Data With Rails

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

Page 48: Synchronizing Core Data With Rails

Configure Mime TypesMime::Type.register_alias "text/html", :iphoneMime::Type.register_alias "text/html", :myiphoneapp_htmlMime::Type.register_alias "application/json", :myiphoneapp_json

Wednesday, July 14, 2010

Page 49: Synchronizing Core Data With Rails

class ApplicationController < ActionController::Base protect_from_forgery before_filter :set_myiphone_app_request after_filter :brand_response_headers protected

def myiphone_app_request? request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/HomeMarks iPhone\/\d\.\d/] end def set_myiphone_app_request if myiphone_app_request? case request.format.symbol when :json then request.format = :myiphoneapp_json when :html then request.format = :myiphoneapp_html end end end

def brand_response_headers response.headers['X-Homemarks'] = '3.0' end

def set_iphone_request request.format = :iphone if iphone_request? end

def iphone_request? !myiphone_app_request? && request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(iPhone|iPod)/] end

def protect_against_forgery? myiphone_app_request? ? false : super end end

CriticalSupport

Wednesday, July 14, 2010

Page 50: Synchronizing Core Data With Rails

The Frontend( part 1 - core data model )

Wednesday, July 14, 2010

Page 51: Synchronizing Core Data With Rails

ObjC RESTful Resource

Wednesday, July 14, 2010

Page 52: Synchronizing Core Data With Rails

ObjC RESTful Resource

Wednesday, July 14, 2010

Page 53: Synchronizing Core Data With Rails

ObjC RESTful Resource

Wednesday, July 14, 2010

Page 54: Synchronizing Core Data With Rails

ObjC RESTful Resource

Wednesday, July 14, 2010

Page 55: Synchronizing Core Data With Rails

ObjC RESTful Resource

Wednesday, July 14, 2010

Page 56: Synchronizing Core Data With Rails

ObjC RESTful Resource

http://code.google.com/p/json-framework/http://allseeing-i.com/ASIHTTPRequest/

Wednesday, July 14, 2010

Page 57: Synchronizing Core Data With Rails

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

Page 58: Synchronizing Core Data With Rails

@implementation MyApp

+ (NSManagedObjectModel *)mom { static NSManagedObjectModel *mom; if (mom == nil) { mom = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]; } return mom;}

+ (NSManagedObjectContext *)moc { static NSManagedObjectContext *moc; if (moc == nil) { NSPersistentStoreCoordinator *psc = [self psc]; if (psc != nil) { moc = [[NSManagedObjectContext alloc] init]; [moc setPersistentStoreCoordinator:psc]; [moc setUndoManager:nil]; [moc setStalenessInterval:3.0]; } } return moc;}

+ (NSManagedObjectContext *)mocImport { // Do same thing above, make sure undo mgr is nil.}

+ (NSPersistentStoreCoordinator *)psc { static NSPersistentStoreCoordinator *psc; if (psc == nil) // Normal PSC code here. return psc;}

@end

CoreData

Wednesday, July 14, 2010

Page 59: Synchronizing Core Data With Rails

#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

Page 60: Synchronizing Core Data With Rails

Taming Core Data

Wednesday, July 14, 2010

Page 61: Synchronizing Core Data With Rails

Taming Core DataSet the class for all your models and generate classes!

Wednesday, July 14, 2010

Page 62: Synchronizing Core Data With Rails

Taming Core DataSet the class for all your models and generate classes!

Subclass NSManagedObject to your name space.

Wednesday, July 14, 2010

Page 63: Synchronizing Core Data With Rails

Taming Core DataSet the class for all your models and generate classes!

Subclass NSManagedObject to your name space.

Make all your generated model classes subclass from above.

Wednesday, July 14, 2010

Page 64: Synchronizing Core Data With Rails

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

Page 65: Synchronizing Core Data With Rails

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

Page 66: Synchronizing Core Data With Rails

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

Page 67: Synchronizing Core Data With Rails

The Frontend( part 2 - external api )

Wednesday, July 14, 2010

Page 68: Synchronizing Core Data With Rails

// Assumes MyRequest & MyFormRequest subclasses of // ASIHTTPRequest and ASIFormDataRequest (minimal use).

@implementation ASIHTTPRequest (MyAdditions)

#pragma mark Designated Initializer

- (id)initWithMyURL:(NSURL *)newURL { self = [self initWithURL:newURL]; self.username = [MyApp userEmail]; self.password = [MyApp userPass]; self.useCookiePersistance = NO; self.useSessionPersistance = NO; self.useKeychainPersistance = NO; self.allowCompressedResponse = YES; self.shouldRedirect = NO; self.requestMethod = @"GET"; self.delegate = [MyApp appDelegate]; self.didFinishSelector = @selector(globalRequestFinished:); self.didFailSelector = @selector(globalRequestFailed:); [self addRequestHeader:@"HTTP_ACCEPT" value:@"application/json"]; [self addRequestHeader:@"User-Agent" value:[MyApp userAgent]]; return self;}

#pragma mark Utility

+ (NSURL *)urlFor:(NSString *)action { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/%@",[MyApp siteHost],action]]; return url;}

+ (MyRequest *)myRequestFor:(NSString *)action { MyRequest *request = [[[MyRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; return request;}

+ (MyFormRequest *)myFormRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [[[MyFormRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; if (params != nil) { for (id key in params) { [request setPostValue:[params objectForKey:key] forKey:key]; } } request.requestMethod = @"POST"; return request;}

+ (MyFormRequest *)myPutRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [self myFormRequestFor:action withParams:params]; request.requestMethod = @"PUT"; return request;}

+ (MyRequest *)myDeleteRequestFor:(NSString *)action { MyRequest *request = [self myRequestFor:action]; request.requestMethod = @"DELETE"; return request;}

@end

Customize To Your API

Wednesday, July 14, 2010

Page 69: Synchronizing Core Data With Rails

// Assumes MyRequest & MyFormRequest subclasses of // ASIHTTPRequest and ASIFormDataRequest (minimal use).

@implementation ASIHTTPRequest (MyAdditions)

#pragma mark Designated Initializer

- (id)initWithMyURL:(NSURL *)newURL { self = [self initWithURL:newURL]; self.username = [MyApp userEmail]; self.password = [MyApp userPass]; self.useCookiePersistance = NO; self.useSessionPersistance = NO; self.useKeychainPersistance = NO; self.allowCompressedResponse = YES; self.shouldRedirect = NO; self.requestMethod = @"GET"; self.delegate = [MyApp appDelegate]; self.didFinishSelector = @selector(globalRequestFinished:); self.didFailSelector = @selector(globalRequestFailed:); [self addRequestHeader:@"HTTP_ACCEPT" value:@"application/json"]; [self addRequestHeader:@"User-Agent" value:[MyApp userAgent]]; return self;}

#pragma mark Utility

+ (NSURL *)urlFor:(NSString *)action { NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/%@",[MyApp siteHost],action]]; return url;}

+ (MyRequest *)myRequestFor:(NSString *)action { MyRequest *request = [[[MyRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; return request;}

+ (MyFormRequest *)myFormRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [[[MyFormRequest alloc] initWithMyURL:[self urlFor:action]] autorelease]; if (params != nil) { for (id key in params) { [request setPostValue:[params objectForKey:key] forKey:key]; } } request.requestMethod = @"POST"; return request;}

+ (MyFormRequest *)myPutRequestFor:(NSString *)action withParams:(NSDictionary *)params { MyFormRequest *request = [self myFormRequestFor:action withParams:params]; request.requestMethod = @"PUT"; return request;}

+ (MyRequest *)myDeleteRequestFor:(NSString *)action { MyRequest *request = [self myRequestFor:action]; request.requestMethod = @"DELETE"; return request;}

@end

Wednesday, July 14, 2010

Page 70: Synchronizing Core Data With Rails

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

Page 71: Synchronizing Core Data With Rails

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

Page 72: Synchronizing Core Data With Rails

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

Page 73: Synchronizing Core Data With Rails

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

Page 74: Synchronizing Core Data With Rails

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

Page 75: Synchronizing Core Data With Rails

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

Page 76: Synchronizing Core Data With Rails

Import On Launch

Wednesday, July 14, 2010

Page 77: Synchronizing Core Data With Rails

Import On Launchclass UsersController < ApplicationController

def home if stale?(:etag => current_user) respond_to do |format| format.html { render :layout => 'application' } format.hmiphoneapp_json { render :json => current_user } end end end end

Wednesday, July 14, 2010

Page 78: Synchronizing Core Data With Rails

Import On Launchclass UsersController < ApplicationController

def home if stale?(:etag => current_user) respond_to do |format| format.html { render :layout => 'application' } format.hmiphoneapp_json { render :json => current_user } end end end end

@implementation MyAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearEtag:) name:NSManagedObjectContextDidSaveNotification object:[HMApp moc]]; //...}

- (void)clearEtag:(NSNotification *)notification { [MyApp removeUserEtag];}

@end

Wednesday, July 14, 2010

Page 79: Synchronizing Core Data With Rails

@implementation MyManagedObject

#pragma mark MyRequest Handling

- (void)createRemoteFinished:(MyRequest *)request { if ([request isSuccessfulMyAppText]) { NSInteger *remoteInt = [[request responseString] integerValue]; NSNumber *remoteId = [NSNumber numberWithInteger:remoteInt]; [self setValue:remoteId forKey:@"remoteId"]; [self save]; }}

@end

@implementation MyRequest

+ (void)columnCreate:(HMColumn *)column { MyFormRequest *request = [self myFormRequestFor:@"columns" withParams:nil]; request.delegate = column; request.didFinishSelector = @selector(createRemoteFinished:); request.didFailSelector = @selector(createRemoteFailed:); [[HMApp queue] addOperation:request];}

@end

Models Drive

Wednesday, July 14, 2010

Page 80: Synchronizing Core Data With Rails

@implementation MyManagedObject

#pragma mark MyRequest Handling

- (void)createRemoteFinished:(MyRequest *)request { if ([request isSuccessfulMyAppText]) { NSInteger *remoteInt = [[request responseString] integerValue]; NSNumber *remoteId = [NSNumber numberWithInteger:remoteInt]; [self setValue:remoteId forKey:@"remoteId"]; [self save]; }}

@end

@implementation HMColumn

+ (void)createRemote { HMColumn *newColumn = [[self class] newObject:nil]; //... [newColumn save]; [MyRequest columnCreate:newColumn]; [newColumn release];}

@end

@implementation MyRequest

+ (void)columnCreate:(HMColumn *)column { MyFormRequest *request = [self myFormRequestFor:@"columns" withParams:nil]; request.delegate = column; request.didFinishSelector = @selector(createRemoteFinished:); request.didFailSelector = @selector(createRemoteFailed:); [[HMApp queue] addOperation:request];}

@end

Models Drive

Wednesday, July 14, 2010

Page 81: Synchronizing Core Data With Rails

@implementation MyManagedObject

#pragma mark MyRequest Handling

- (void)createRemoteFinished:(MyRequest *)request { if ([request isSuccessfulMyAppText]) { NSInteger *remoteInt = [[request responseString] integerValue]; NSNumber *remoteId = [NSNumber numberWithInteger:remoteInt]; [self setValue:remoteId forKey:@"remoteId"]; [self save]; }}

@end

@implementation HMColumn

+ (void)createRemote { HMColumn *newColumn = [[self class] newObject:nil]; //... [newColumn save]; [MyRequest columnCreate:newColumn]; [newColumn release];}

@end

@implementation MyRequest

+ (void)columnCreate:(HMColumn *)column { MyFormRequest *request = [self myFormRequestFor:@"columns" withParams:nil]; request.delegate = column; request.didFinishSelector = @selector(createRemoteFinished:); request.didFailSelector = @selector(createRemoteFailed:); [[HMApp queue] addOperation:request];}

@end

Wednesday, July 14, 2010

Page 82: Synchronizing Core Data With Rails

@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

Page 83: Synchronizing Core Data With Rails

The End( liked my talk – buy my app - write a review )

Wednesday, July 14, 2010