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 isolati...
The "AJAX Head"
       Design Pattern
                        “e AJAX head design pattern
                        forces ...
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
Atla...
Domain
Model
Columns, Boxes,
Bookmarks

Single Page Load

Everything Done
Client-Side With
JavaScript

Parallels With     ...
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
 wri...
Data-Interchange... JSON
JSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and
 wri...
JSON in Ruby/Rails
        class Object
          def to_json(options = nil)
            ActiveSupport::JSON.encode(
     ...
JSON in Ruby/Rails
Specifically in ActiveSupport
require 'active_support/json'   class Object
                            ...
JSON in Ruby/Rails
Specifically in ActiveSupport
require 'active_support/json'              class Object
                 ...
JSON in Ruby/Rails
Specifically in ActiveSupport
require 'active_support/json'              class Object
                 ...
JSON in Ruby/Rails
Specifically in ActiveSupport
require 'active_support/json'              class Object
                 ...
JSON in ActiveRecord
class Bookmark < ActiveRecord::Base

 JSON_ATTRS = ['id','owner_id','owner_type','url','name','positi...
JSON in ActiveRecord
class Bookmark < ActiveRecord::Base

 JSON_ATTRS = ['id','owner_id','owner_type','url','name','positi...
JSON in ActiveRecord
>> Bookmark.find(27726).to_json
=> "{"position 1,
                  ": "name "Prototype
             ...
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
                ...
The Backend
  ( part 2 - web app controller )
Changing State
Changing State

       CREATE   READ     UPDATE   DELETE

DB     INSERT   SELECT   UPDATE   DELETE

HTTP    POST     GET  ...
Changing State

       CREATE   READ     UPDATE   DELETE

DB     INSERT   SELECT   UPDATE   DELETE

HTTP    POST     GET  ...
Changing State

       CREATE   READ     UPDATE   DELETE

DB     INSERT   SELECT   UPDATE   DELETE

HTTP    POST     GET  ...
Changing State

        CREATE    READ     UPDATE   DELETE

 DB     INSERT    SELECT   UPDATE   DELETE

HTTP     POST     ...
Resource
Routes
Resource
Routes     Homemarks::Application.routes.draw do |map|

             resources :users
             resource :sess...
Resource
Routes              Homemarks::Application.routes.draw do |map|

                      resources :users
         ...
Resource
Routes              Homemarks::Application.routes.draw do |map|

                      resources :users
         ...
Resource
Routes              Homemarks::Application.routes.draw do |map|

                      resources :users
         ...
Resource
Routes              Homemarks::Application.routes.draw do |map|

                      resources :users
         ...
Generated URLs
$ rake routes CONTROLLER=users

GET      /users(.:format)            {:controller=>"users",   :action=>"ind...
RESTful Actions
class BoxesController < ApplicationController

 def create
   @box = current_user.columns.find(params[:col...
Configure Mime Types
Mime::Type.register_alias "text/html", :iphone
Mime::Type.register_alias "text/html", :myiphoneapp_ht...
class ApplicationController < ActionController::Base




Critical
            protect_from_forgery
            before_filt...
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...
Core
       @implementation MyApp

       + (NSManagedObjectModel *)mom {
         static NSManagedObjectModel *mom;
     ...
#pragma mark Network

+ (ASINetworkQueue *)queue {


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

Ma...
Less Wizardry
@implementation MyManagedObject

#pragma mark Reflection

+ (NSEntityDescription *)entity {
  return [[[MyAp...
Less Wizardry
@implementation MyManagedObject

#pragma mark Finders

+ (MyManagedObject *)objectWithID:(NSString *)anId {
...
Create/Import Helpers
@implementation MyManagedObject

#pragma mark Creators

+ (id)newObject:(NSDictionary *)attributes {...
The Frontend
   ( part 2 - external api )
Customize To Your API
// Assumes MyRequest & MyFormRequest subclasses of
// ASIHTTPRequest and ASIHTTPFormRequest (minimal...
}

#pragma mark Utility

+ (NSURL *)urlFor:(NSString *)action {
  NSURL *url = [NSURL URLWithString:[NSString stringWithFo...
Import On Launch
@implementation ASIHTTPRequest (MyAdditions)

+ (void)getUserDataFor:(id)aDelegate {
  MyRequest *request...
Import On Launch
@implementation ASIHTTPRequest (MyAdditions)

+ (void)getUserDataFor:(id)aDelegate {
  MyRequest *request...
Import On Launch
            The Controller (request delegate)
- (void)didGetUserData:(MyRequest *)request {
  if ([reques...
Import On Launch
            The Controller (request delegate)
- (void)didGetUserData:(MyRequest *)request {
  if ([reques...
Import On Launch
               The Controller (request delegate)
  - (void)didGetUserData:(MyRequest *)request {
    if (...
Import On Launch
               The Controller (request delegate)
  - (void)didGetUserData:(MyRequest *)request {
    if (...
Import On Launch
Import On Launch
class UsersController < ApplicationController

 def home
   if stale?(:etag => current_user)
     respond...
Import On Launch
class UsersController < ApplicationController

 def home
   if stale?(:etag => current_user)
     respond...
Models Drive
@implementation MyManagedObject

#pragma mark MyRequest Handling

- (void)createRemoteFinished:(MyRequest *)r...
Models Drive
@implementation MyManagedObject

#pragma mark MyRequest Handling

- (void)createRemoteFinished:(MyRequest *)r...
@implementation HMColumn

+ (void)createRemote {
  HMColumn *newColumn = [[self class] newObject:nil];
  //...
  [newColum...
Models Drive (box example)
@implementation HMBox

- (void)colorize {
  if ([[self changedValues] hasKey:@"style"] && [self...
The End
( liked my talk – buy my app - write a review )
Synchronizing Core Data With Rails
Synchronizing Core Data With Rails
Synchronizing Core Data With Rails
Synchronizing Core Data With Rails
Synchronizing Core Data With Rails
Synchronizing Core Data With Rails
Upcoming SlideShare
Loading in...5
×

Synchronizing Core Data With Rails

24,025

Published on

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.

Published in: Technology
0 Comments
27 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
24,025
On Slideshare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
492
Comments
0
Likes
27
Embeds 0
No embeds

No notes for slide

Synchronizing Core Data With Rails

  1. 1. Synchronizing Core Data With Rails Ken Collins metaskills.net
  2. 2. Real World Example
  3. 3. The "AJAX Head" Design Pattern
  4. 4. The "AJAX Head" Design Pattern
  5. 5. 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.”
  6. 6. 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
  7. 7. Domain Model
  8. 8. Domain Model Columns, Boxes, Bookmarks
  9. 9. Domain Model Columns, Boxes, Bookmarks Single Page Load
  10. 10. Domain Model Columns, Boxes, Bookmarks Single Page Load Everything Done Client-Side With JavaScript
  11. 11. Domain Model Columns, Boxes, Bookmarks Single Page Load Everything Done Client-Side With JavaScript Parallels With Atlas & Cappuccino
  12. 12. Domain Model Columns, Boxes, Bookmarks Single Page Load Everything Done Client-Side With JavaScript Parallels With http://cappuccino.org/ Atlas & Cappuccino http://280atlas.com/
  13. 13. The Backend ( part 1 - web app model )
  14. 14. 3.0.0.p re
  15. 15. Data-Interchange... JSON
  16. 16. 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.
  17. 17. 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/
  18. 18. 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"}
  19. 19. 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"}
  20. 20. 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"}
  21. 21. 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"}
  22. 22. 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.
  23. 23. 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
  24. 24. 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
  25. 25. 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 ": "}"
  26. 26. JSON in ActiveRecord Don’t Over think! There Are Tools For Everything Use Them
  27. 27. 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>
  28. 28. The Backend ( part 2 - web app controller )
  29. 29. Changing State
  30. 30. Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE
  31. 31. Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE
  32. 32. Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE
  33. 33. Changing State CREATE READ UPDATE DELETE DB INSERT SELECT UPDATE DELETE HTTP POST GET PUT DELETE Representational State Transfer (REST)
  34. 34. Resource Routes
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. 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"}
  41. 41. 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
  42. 42. 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
  43. 43. 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
  44. 44. The Frontend ( part 1 - core data model )
  45. 45. ObjC RESTful Resource
  46. 46. ObjC RESTful Resource
  47. 47. ObjC RESTful Resource
  48. 48. ObjC RESTful Resource
  49. 49. ObjC RESTful Resource
  50. 50. ObjC RESTful Resource http://code.google.com/p/json-framework/ http:/ /allseeing-i.com/ASIHTTPRequest/
  51. 51. 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
  52. 52. 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
  53. 53. #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; }
  54. 54. Taming Core Data
  55. 55. Taming Core Data Set the class for all your models and generate classes!
  56. 56. Taming Core Data Set the class for all your models and generate classes! Subclass NSManagedObject to your name space.
  57. 57. 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.
  58. 58. 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
  59. 59. 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
  60. 60. 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
  61. 61. The Frontend ( part 2 - external api )
  62. 62. 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; }
  63. 63. } #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
  64. 64. 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
  65. 65. 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
  66. 66. 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]; } }
  67. 67. 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]; } }
  68. 68. 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)"
  69. 69. 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)"
  70. 70. Import On Launch
  71. 71. 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
  72. 72. 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
  73. 73. 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
  74. 74. 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
  75. 75. @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
  76. 76. 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
  77. 77. The End ( liked my talk – buy my app - write a review )
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×