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.
4. Why Write Web
Applications For The
Web Only?
Wednesday, July 14, 2010
5. Why Write Web
Applications For The
Web <html> Only?
Wednesday, July 14, 2010
6. Why Write Web
Applications For The
Web <html> Only?
THE BACK-END (server)
Rails Web Application
Wednesday, July 14, 2010
7. Why Write Web
Applications For The
Web <html> Only?
THE BACK-END (server) THE FRONT-END (clients)
Rails Web Application Browser - JavaScript
iPhone - Objective-C
Wednesday, July 14, 2010
8. Why Write Web
Applications For The
Web <html> Only?
THE BACK-END (server) THE FRONT-END (clients)
Rails Web Application Browser - JavaScript
iPhone - Objective-C
Wednesday, July 14, 2010
11. The "AJAX Head"
Design Pattern
βξe AJAX head design pattern forces the view and
controller to work in isolation with the most minimal
coupling possible. Kind of like a web service.β
β...an implementation pattern that drastically
modi es common web client-server interactions in
order to bring them more closely in line with
enterprise client-server interactions...β
Wednesday, July 14, 2010
12. The "AJAX Head"
Design Pattern
βξ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...β
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
Wednesday, July 14, 2010
13. Why Write Web
Applications For The
Web <html> Only?
THE BACK-END (server) THE FRONT-END (clients)
Rails Web Application Browser - JavaScript
iPhone - Objective-C
Wednesday, July 14, 2010
14. Why Write Web
Applications For The
Web <html> Only?
THE BACK-END (server) THE FRONT-END (clients)
Rails Web Application Browser - JavaScript
iPhone - Objective-C
1) AR Model
2) Controller
Wednesday, July 14, 2010
15. Why Write Web
Applications For The
Web <html> Only?
THE BACK-END (server) THE FRONT-END (clients)
Rails Web Application Browser - JavaScript
iPhone - Objective-C
1) AR Model 1) CoreData Models
2) Controller 2) External API
Wednesday, July 14, 2010
16. The Backend
( part 1 - web app model )
Wednesday, July 14, 2010
22. Data-Interchange... JSON
JSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and
write. It is easy for machines to parse and generate. JSON is a text format that is completely
language independent but uses conventions that are familiar to programmers of the C-family
of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.
Wednesday, July 14, 2010
23. Data-Interchange... JSON
JSON (JavaScript Object Notation) is a lightweight format. It is easy for humans to read and
write. It is easy for machines to parse and generate. JSON is a text format that is completely
language independent but uses conventions that are familiar to programmers of the C-family
of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.
http://www.json.org/
Wednesday, July 14, 2010
24. JSON in Ruby/Rails
class Object
def to_json(options = nil)
ActiveSupport::JSON.encode(
self,options)
end
def as_json(options = nil)
instance_values
end
end
class Foo
attr_accessor :bar
end
f = Foo.new
f.bar = 'batz'
puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
25. JSON in Ruby/Rails
Specifically in ActiveSupport
require 'active_support/json' class Object
def to_json(options = nil)
ActiveSupport::JSON.encode(
self,options)
end
def as_json(options = nil)
instance_values
end
end
class Foo
attr_accessor :bar
end
f = Foo.new
f.bar = 'batz'
puts f.to_json # => {"bar":"batz"}
Wednesday, July 14, 2010
26. JSON in Ruby/Rails
Specifically in ActiveSupport
require 'active_support/json' 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"}
Wednesday, July 14, 2010
27. 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"}
Wednesday, July 14, 2010
28. 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.
Wednesday, July 14, 2010
29. 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
Wednesday, July 14, 2010
30. JSON in ActiveRecord
class Bookmark < ActiveRecord::Base
JSON_ATTRS = ['id','owner_id','owner_type','url','name','position']
def as_json(options=nil)
attributes.slice(*JSON_ATTRS)
end
end
class Box < ActiveRecord::Base
JSON_ATTRS = ['id','column_id','title','style','collapsed','position']
belongs_to :column
has_many :bookmarks, :as => :owner, :order => 'position'
acts_as_list :scope => :column_id
def as_json(options=nil)
attributes.slice(*JSON_ATTRS).merge(:bookmarks => bookmarks)
end
end
Wednesday, July 14, 2010
36. Changing State
CREATE READ UPDATE DELETE
DB INSERT SELECT UPDATE DELETE
HTTP POST GET PUT DELETE
Wednesday, July 14, 2010
37. Changing State
CREATE READ UPDATE DELETE
DB INSERT SELECT UPDATE DELETE
HTTP POST GET PUT DELETE
Wednesday, July 14, 2010
38. Changing State
CREATE READ UPDATE DELETE
DB INSERT SELECT UPDATE DELETE
HTTP POST GET PUT DELETE
Wednesday, July 14, 2010
39. Changing State
CREATE READ UPDATE DELETE
DB INSERT SELECT UPDATE DELETE
HTTP POST GET PUT DELETE
Representational State Transfer (REST)
Wednesday, July 14, 2010
41. Resource
Routes Homemarks::Application.routes.draw do |map|
resources :users
resource :session
resources :columns do
collection { put :sort }
member { delete :destroy_boxes }
end
resources :boxes do
collection { put :sort }
member do
put :toggle_collapse
put :colorize
put :change_title
end
end
end
Wednesday, July 14, 2010
42. Resource
Routes 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
Wednesday, July 14, 2010
43. 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
Wednesday, July 14, 2010
44. 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
Wednesday, July 14, 2010
45. 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
Wednesday, July 14, 2010
46. Generated URLs
$ rake routes CONTROLLER=users
GET /users(.:format) {:controller=>"users", :action=>"index"}
POST /users(.:format) {:controller=>"users", :action=>"create"}
GET /users/new(.:format) {:controller=>"users", :action=>"new"}
GET /users/:id(.:format) {:controller=>"users", :action=>"show"}
PUT /users/:id(.:format) {:controller=>"users", :action=>"update"}
DELETE /users/:id(.:format) {:controller=>"users", :action=>"destroy"}
GET /users/:id/edit(.:format) {:controller=>"users", :action=>"edit"}
Wednesday, July 14, 2010
47. 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
Wednesday, July 14, 2010
49. class ApplicationController < ActionController::Base
Critical
protect_from_forgery
before_filter :set_myiphone_app_request
after_filter :brand_response_headers
protected
Support def myiphone_app_request?
request.env["HTTP_USER_AGENT"] &&
end
request.env["HTTP_USER_AGENT"][/HomeMarks iPhone/d.d/]
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
Wednesday, July 14, 2010
50. The Frontend
( part 1 - core data model )
Wednesday, July 14, 2010
61. Taming Core Data
Set the class for all
your models and
generate classes!
Wednesday, July 14, 2010
62. Taming Core Data
Set the class for all
your models and
generate classes!
Subclass
NSManagedObject
to your name space.
Wednesday, July 14, 2010
63. Taming Core Data
Set the class for all
your models and
generate classes!
Subclass
NSManagedObject
to your name space.
Make all your
generated model
classes subclass
from above.
Wednesday, July 14, 2010
77. Import On Launch
class UsersController < ApplicationController
def home
if stale?(:etag => current_user)
respond_to do |format|
format.html { render :layout => 'application' }
format.hmiphoneapp_json { render :json => current_user }
end
end
end
end
Wednesday, July 14, 2010
78. Import On Launch
class UsersController < ApplicationController
def home
if stale?(:etag => current_user)
respond_to do |format|
format.html { render :layout => 'application' }
format.hmiphoneapp_json { render :json => current_user }
end
end
end
end
@implementation MyAppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearEtag:)
name:NSManagedObjectContextDidSaveNotification
object:[HMApp moc]];
//...
}
- (void)clearEtag:(NSNotification *)notification {
[MyApp removeUserEtag];
}
@end
Wednesday, July 14, 2010