iPhone, Meet Rails... Chat Amongst Yourselves...
Today's accomplishment is seemless iPhone to Rails communication. By taking advantage of the awesome iPhoneOnRails library, forking and updating the dead-simple Devise Authentication Gem, I'm able to use the iPhone as a thin client while keeping state and doing all of the heaving lifting server side.
Things I Did:
- Install iPhoneOnRails framework.
- Forked and updated Devise gem.
- Sync'ed user sessions between Rails & iPhone.
My first step was to get the Devise gem installed on Rails. As usual, I spent 5 minutes refreshing my memory, thanks to Ryan over at Railscasts. After the refresher, I dropped:
config.gem "devise", :version => "1.0.8"
config.gem "warden", :version => "0.10.7"
into my environment.rb file, and ran:
- rake gems:install
- script/generate devise_install
- script/generate devise User
- rake db:migrate
- added a before_filter to a controller
Jumped over to http://localhost:3000/users/sign_up and instantly had user authentication rockin.
Moving on... If you don't know it yet, iPhoneOnRails is where it's at for quick iPhone development with a Rails app. It's a framework you drop into your iPhone apps allowing you take advantage of all the restful CRUDy goodness that Rails has to offer.
Product.h:
#import "ObjectiveResource.h"
@interface Product : NSObject {
NSString *productId;
NSString *name;
NSString *formattedPrice;
NSString *url;
NSString *smallImageLogo;
NSDate *updatedAt;
NSDate *createdAt;
}
@property (nonatomic, retain) NSDate *createdAt;
@property (nonatomic, retain) NSDate *updatedAt;
@property (nonatomic, retain) NSString *productId;
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *url;
@property (nonatomic, retain) NSString *smallImageLogo;
@property (nonatomic, retain) NSString *formattedPrice;
@end
Product.m:
#import "Product.h"
#import "ObjectiveResource.h"
@implementation Product
@synthesize createdAt , updatedAt , name, productId;
@synthesize formattedPrice, url, smallImageLogo;
- (void) dealloc {
[createdAt release];
[updatedAt release];
[name release];
[url release];
[formattedPrice release];
[smallImageLogo release];
[productId release];
[super dealloc];
}
@end
Fetch data from the Rails application:
NSMutableArray *products = [Products findAllRemote];
Which tells the iPhone framework to hit the Rails products_controller.rb restful route:
class ProductsController < ApplicationController
def show
@products = Product.all
respond_to do |format|
format.html
format.xml { render :xml => @products }
format.json { render :json => @products }
end
end
And the iPhone now has a nice little array populated with product objects from the Rails app!
Product *thisProduct = [products objectAtIndex:indexPath.row]
cell.nameLabel.text = thisProduct.name;
cell.urlLabel.text = thisProduct.url;
cell.priceLabel.text = thisProduct.formattedPrice;
cell.imageLogo.image = thisProduct.smallImageLogo;
But wait! There's more...
What if the products controller is password protected, with say, the Device authentication gem? Never fear!
ApplicationDelegate.m:
[ObjectiveResourceConfig setUser:@"joe@example.com"];
[ObjectiveResourceConfig setPassword:@"pie"];
Rails:
class User < ActiveRecord::Base
devise :http_authenticatable
[...]
end
class ProductsController < ApplicationController
before_filter :authenticate_user!
[...]
end
Now, see the ":http_authenticatable" in the User class? That tell's Devise that it should expect an HTTP Basic Authentication request with the username and password in it. And the ProductsController assures that every action is authenticated with it's before_fileter.
Now, the iPhoneOnRails framework takes the first request and shoots up the provided username/password via the header. Once authenticated, it's tracks the session via a cookie. So, sooooo.... nice :)
At this point we should have a fully functional authentication system working on the web browser AND the iPhone. But we don't have it just yet. Now what the iPhone framework will do for us is bundle every request into an xml file. Intead of calling http://localhost:3000/users, like you would via a web browser, it calls http://lcoalhost:3000/users.xml. This of course allows Rails restful routes to kick in and return the iPhone app a nicly formeted XML file. Problem I hit was with Devise.
So I'm running Rails 2.3.8, and the latest Devise release for Rails 2.x is Devise 1.0.8 - unfortunatly, there is no support for such resetful requests via XML in that release of Devise (never looked into the Rails 3.x Devise branch, but I don't think it exists there either).
So what am I to do?
Fork it!
You can find my Forked Devise on my GitHub. I havent fully tested it at the time of this writing, so I have yet to send a pull request to the Devise maintainers, but I will once it's been tested a bit more. For now, it's working great for my needs. GitHub rocks!
And there it is. iPhone to Rails authentication and session managment!
Totally Unrelated Conclusion:
Here's how Japanese truck drivers roll:
DEKOTORA trucks: