I Phone On Rails
-
Upload
john-wilker -
Category
Technology
-
view
3.573 -
download
1
description
Transcript of I Phone On Rails
iPhone on Rails
Mike Clark clarkware.com
(or Conventions Matter)
Hosted Rails App?
Fielded iPhone App?
Neither?!
RailsInstallation
$ sudo gem update --system
$ sudo gem install rails
$ sudo gem update rake
$ sudo gem update sqlite3-ruby
$ rails expenses
$ cd expenses
$ script/generate scaffold expense \ name:string amount:decimal
$ rake db:migrate
$ script/server
http://localhost:3000/expenses
RailsScaffold App
ActionController::Routing::Routes.draw do |map| map.resources :expensesend
Rails ResourceRoutes
POST
GET
PUT
DELETE
/expenses
/expenses/3
/expenses/3
/expenses/3
Rails ResourceCRUD Routing
class ExpensesController < ApplicationController
# GET /expenses def index end
# POST /expenses def create end # GET /expenses/3 def show end
# PUT /expenses/3 def update end
# DELETE /expenses/3 def destroy end end
Rails ResourceController
Expense.create(:name => "iPod touch", :amount => 249.00)
Expense.find(3)
Expense.update(3, :amount => 199.00)
Expense.destroy(3)
Rails ResourceModel
class Expense < ActiveRecord::Base validates_presence_of :name validates_numericality_of :amount, :greater_than_or_equal_to => 0end
Rails ResourceView
<p> <b>Name:</b> <%=h @expense.name %></p>
<p> <b>Amount:</b> <%=h @expense.amount %></p>
<%= link_to 'Edit', edit_expense_path(@expense) %> |<%= link_to 'Back', expenses_path %>
GET
find
SELECT
POST PUT DELETE
create
INSERT
update
UPDATE
destroy
DELETE
RailsCRUD Conventions
>> print Expense.find(3).to_xml
<?xml version="1.0" encoding="UTF-8"?><expense> <id type="integer">2</id> <name>iPod touch</name> <amount type="decimal">199.0</amount> <created-at type="datetime">2009-09-15T16:01:54Z</created-at> <updated-at type="datetime">2009-09-15T16:01:54Z</updated-at></expense>
>> print Expense.find(3).to_json
{"expense": {"id":2, "name":"iPod touch", "amount":199.0, "created_at":"2009-09-15T16:01:54Z", "updated_at":"2009-09-15T16:01:54Z" }}
class ExpensesController < ApplicationController
def show @expense = Expense.find(params[:id])
respond_to do |format| format.html # show.html.erb format.xml { render :xml => @expense } format.json { render :json => @expense } end end
end
GET /expenses/3.{html|xml|json}
Rails ResourceMulti-Format Responses
class User < ActiveRecord::Base def to_xml(options={}) options[:only] = [:id, :name, :screen_name] super(options) end end
RailsSecurity Tip
iPhoneScaffolding
iPhoneCRUD
iPhone CRUDRoll Your Own
@implementation Expense
static NSString *siteURL = @"http://localhost:3000";
- (NSString *)params { return [NSString stringWithFormat:@"{\"expense\":{\"name\":\"%@\",\"amount\":\"%@\"}}", self.name, self.amount];}
- (void)createRemote { NSString *url = [NSString stringWithFormat:@"%@/expenses.json", siteURL]; [Resource post:[self params] to:url];}
+ (NSArray *)findAllRemote { NSString *url = [NSString stringWithFormat:@"%@/expenses.json", siteURL]; NSString *jsonString = [Resource get:url]; NSArray *expenses = // create expenses from JSON string return expenses;}
- (void)updateRemote { NSString *url = [NSString stringWithFormat:@"%@/expenses/%@.json", siteURL, self.expenseId]; [Resource put:[self params] to:url];}
- (void)destroyRemote { NSString *url = [NSString stringWithFormat:@"%@/expenses/%@.json", siteURL, self.expenseId]; [Resource delete:url];}
@end
http://github.com/Caged/httpriot
HTTPRiot
Supports generic RESTful operationsConverts JSON response to NSDictionary
AsynchronousYou decide what to do with the data
[HRRestModel setDelegate:someObject];[HRRestModel setBaseURL:[NSURL URLWithString:@"http://your-server/api"]];
[HRRestModel getPath:@"/expenses.json" withOptions:nil object:nil];
NSDictionary *options = [NSDictionary dictionaryWithObject:[expense JSONRepresentation] forKey:@"body"];[HRRestModel postPath:@"/expenses" withOptions: options object:nil];
NSDictionary * options = [NSDictionary dictionaryWithObject:[expense JSONRepresentation] forKey:@"body"];[HRRestModel putPath:@"/expenses/3" withOptions:options object:nil];
[HRRestModel deletePath:@"/expenses/3" withOptions:nil object:nil];
HTTPRiotConfiguration and Requests
HTTPRiotCallbacks
- (void)restConnection:(NSURLConnection *)connection didReturnResource:(id)resource object:(id)object { for (id item in resource) { Expense *expense = [[Expense alloc] initWithDictionary:item]; // do something with expenses }}
- (void)restConnection:(NSURLConnection *)connection didFailWithError:(NSError *)error object:(id)object { // Handle connection errors}
- (void)restConnection:(NSURLConnection *)connection didReceiveError:(NSError *)error response:(NSHTTPURLResponse *)response object:(id)object { // Handle invalid responses: 404, 500, and so on}
- (void)restConnection:(NSURLConnection *)connection didReceiveParseError:(NSError *)error responseBody:(NSString *)string { // Request was successful, but couldn't parse the data returned by the server}
http://github.com/pokeb/asi-http-request
ASIHTTPRequest
Super flexible with lots of features
Uses CFNetwork API
File uploads, progress indictors, etc.
Asynchronous
ASIHTTPRequestUsage
- (void)sendRequest { NSURL *url = [NSURL URLWithString:@"http://your-server.com/expenses.json"]; ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:url]; [request setDelegate:self]; [request setDidFinishSelector:@selector(requestSucceeded:)]; [request setDidFailSelector:@selector(requestFailed:)]; [[self queue] addOperation:request]; [request release];} - (void)requestSucceeded:(ASIHTTPRequest *)request { NSString *response = [request responseString];} - (void)requestFailed:(ASIHTTPRequest *)request { NSError *error = [request error];}
http://github.com/yfactorial/objectiveresource
ObjectiveResource
Objective-C port of ActiveResource
Assumes Rails RESTful conventions
Serializes to/from objects (XML or JSON)
Asynchronous option in 1.1 branch
ObjectiveResourceRemote Resource
#import "ObjectiveResource.h"
@interface Expense : NSObject { NSString *expenseId; NSString *name; NSString *amount; NSDate *createdAt; NSDate *updatedAt;}
@property (nonatomic, copy) NSString *expenseId;@property (nonatomic, copy) NSString *name;@property (nonatomic, copy) NSString *amount;@property (nonatomic, retain) NSDate *createdAt;@property (nonatomic, retain) NSDate *updatedAt; @end
* NSObject+ObjectiveResource category
ObjectiveResourceConfiguration
#import "ObjectiveResource.h" [ObjectiveResourceConfig setSite:@"http://your-server.com/"];[ObjectiveResourceConfig setResponseType:JSONResponse];
ObjectiveResourceCRUD Operations
NSArray *expenses = [Expense findAllRemote];
Expense *expense = [[Expense alloc] init];expense.name = @"iPod touch";[expense createRemote];
Expense *expense = [Expense findRemote:@"3"];expense.name = @"iPhone";[expense updateRemote];
[expense destroyRemote];
ObjectiveResourceAsynchronous Way
- (void)loadExpenses { self.expenses = [Expense findAllRemote]; [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;}
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;[[ConnectionManager sharedInstance] runJob:@selector(loadExpenses) onTarget:self];
* NSInvocationOperation in an NSOperationQueue
iPhoneNested Resources
RailsNested Resources
ActionController::Routing::Routes.draw do |map| map.resources :budgets, :has_many => :expensesend
RailsNested Resource
class ExpensesController < ApplicationController before_filter :find_budget
def show @expense = @budget.expenses.find(params[:id])
respond_to do |format| format.html format.xml { render :xml => @expense } format.json { render :json => @expense } end end
private
def find_budget @budget = current_user.budgets.find(params[:budget_id]) end
end
GET /budgets/6/expenses
ObjectiveResourceNested Resources
@implementation Budget
@synthesize budgetId;
- (NSArray *)findAllExpenses { return [Expense findRemote:[NSString stringWithFormat:@"%@/%@", self.budgetId, @"expenses"]];}
@end
GET /budgets/6/expenses
ObjectiveResourceNested Resources
@implementation Expense
@synthesize expenseId;@synthesize budgetId;
+ (NSString *)getRemoteCollectionName { return @"budgets";}
- (NSString *)nestedPath { NSString *path = [NSString stringWithFormat:@"%@/expenses", self.budgetId]; if (self.expenseId) { path = [path stringByAppendingFormat:@"/%@", self.expenseId]; } return path;}
- (BOOL)createRemoteWithResponse:(NSError **)aError { return [self createRemoteAtPath:[[self class] getRemoteElementPath:[self nestedPath]] withResponse:aError];}
- (BOOL)updateRemoteWithResponse:(NSError **)aError { return [self updateRemoteAtPath:[[self class] getRemoteElementPath:[self nestedPath]] withResponse:aError];}
- (BOOL)destroyRemoteWithResponse:(NSError **)aError { return [self destroyRemoteAtPath:[[self class] getRemoteElementPath:[self nestedPath]] withResponse:aError];}
@end* yeah, itʼs kinda clunky
iPhone & RailsAuthentication
RailsAuthentication
class BudgetsController < ApplicationController
before_filter :authenticate def index @budgets = current_user.budgets.all end
def show @budget = current_user.budgets.find(params[:id]) end
private
def current_user @current_user ||= User.find(session[:user_id]) end
end
RailsAuthentication
def authenticate return if session[:user_id] respond_to do |format| format.html do redirect_to login_url end format.any(:xml, :json) do user = authenticate_with_http_basic do |username, password| User.authenticate(username, password) end if user session[:user_id] = user.id else request_http_basic_authentication end end end end
ObjectiveResourceConfiguration
#import "ObjectiveResource.h" [ObjectiveResourceConfig setSite:@"http://your-server.com/"];[ObjectiveResourceConfig setResponseType:JSONResponse];[ObjectiveResourceConfig setUser:@"John"];[ObjectiveResourceConfig setPassword:@"Appleseed"];
RESTful web services are resource based, stateless,
and scaleable
http://github.com/clarkware
pragmaticstudio.com
Hands-on iPhone and Rails training from the folks who
wrote the books