iPhone on Rails

by James

It’s really simple to get a Rails application to serve custom views to an iPhone or iPod touch: so simple, in fact, that in a spare half-an-hour I wrapped the necessary code up into a tiny plugin, which will do all the work for you!

Update: if you’re using the plugin, have questions, or have found this post useful, take a moment to send me a message on Github, or email me (james at this domain). It’s always great to know how people are using my work :)

If you just want to get up and running as fast as possible, skip to the Plugin section at the end of the article. Feedback is always appreciated; if you use the plugin, or have questions, feel free to email me (james at this domain).

If you want to understand how the plugin works, read the full article.

Three Simple Steps

There are basically three steps to serving custom iPhone content from Rails:

  1. Register a custom iphone format
  2. Tell Rails how to recognize an iPhone request
  3. Tell Rails to use the custom format for iPhone requests

Registering a custom iPhone format

Rails already knows about formats like HTML and XML, and all iPhones actually need from a Rails app is plain old HTML — but we need a way to distinguish our iPhone HTML from the HTML we send to other browsers.

To do this, we register a mime type alias — just a way of telling Rails that when we say ‘iphone’, we mean ‘HTML’. This is done in config/initializers/mime_types.rb:

[ruby]
Mime::Type.register_alias “text/html”, :iphone
[/ruby]

Now we can do something like format.iphone and Rails will know we want it to serve our views as HTML.

Tell Rails how to recognize an iPhone request

In order to serve custom views to iPhones, we need a way of recgonizing when someone is viewing our website on an iPhone. This is easy:

[ruby]
class ApplicationController < ActionController::Base

# other code here

private
# detect iphone requests
def iphone_request?
(agent = request.env["HTTP_USER_AGENT"]) &&
agent[/(Mobile\/.+Safari)/]
end
end
[/ruby]

The iphone_request? method looks for MobileSafari’s user agent string in the incoming HTTP request. Because MobileSafari runs on the iPod Touch as well as on the iPhone, this method will ensure that we serve our custom views to both devices.

Now we can identify iPhone requests, we just need to put everything together and…

Tell Rails to use our custom format for iPhone requests

Normally, when Rails receives a request from a browser, it determines what sort of response the browser is looking for (XML, HTTP, JSON, etc). Then it looks for corresponding templates — index.html.erb, index.xml.erb, etc. This behaviour is customizable using ActionController’s respond_to method.

In order to serve custom views to the iPhone, we need to tell Rails to switch to our custom :iphone format whenever an iPhone visits our website. This sounds complicated, but is easy:

[ruby]
class ApplicationController < ActionController::Base

before_filter :adjust_format_for_iphone_requests

private
def adjust_format_for_iphone_requests
request.format = :iphone if iphone_request?
end
end
[/ruby]

This little snippet calls our iphone_request? method and tells Rails to use our iphone format where appropriate.

That’s really all there is to it. You can now create index.iphone.erb, show.iphone.erb, etc, alongside your standard HTML views and layouts, and Rails will serve them up to iPhone users. Bon appetit!

The Plugin

Serving custom iPhone content is something I want to do from many Rails apps, so I wrapped the functionality outlined above into a plugin for quick and easy iphoneification. Install thus:

[bash]
script/plugin install git://github.com/jameswilding/iphoneification.git
[/bash]

To use the plugin, just add one line to your application controller:

[ruby]
class ApplicationController
responds_to_iphone
end
[/ruby]

If there’s a particular controller or action that you don’t want to serve iPhone content from, use

[ruby]
class NotAnIPhoneController
skip_iphone_response
end
[/ruby]

The plugin does most of the work: all you need to do is name your custom iPhone template {template_name}.iphone.erb.