Facebook templates made easy with Rails 2.0 custom Mime types
I’ve written elsewhere about how I used my own lightweight library to add Facebook functionality to Autopendium :Stuff About Old Cars, the classic car community website I run.
The library has made it fairly easy to keep up with Facebook’s many changes, and the Facebook app has been a good marketing tool for the site. But adding more functionality to the app has meant duplicating code, as all the actions are handled by a FacebookController.
However, now that I’ve updated to Rails 2.0, adding Facebook functionality is a whole lot easier, and the solution is so simple, I’m sure it’s a common usage pattern.
I don’t really want to do an events action in the FacebookController just to make it available in the Facebook app; I’d rather just use the EventsController#index action and render it with a custom template. (We’re already doing something similar for ics MimeTypes — serving up the events in iCalendar format, so they can be imported directly into your electronic calendar, but that’s for another post).
Custom Mime Types to the rescue
With the new custom Mime types in Rails 2.0, it’s a breeze. Often these are used for customising apps for the iPhone (as shown here), but I reckoned the situation was pretty similar with integrating Facebook interfaces to existing apps.
If the request comes in via the the Facebook canvas we need all the custom Facebook FBML to specify style, etc (and even if you’re using an iframe, you’ll probably want a custom layout).
So this is what I did. First, add a custom facebook mimetype in your environment file:
Mime::Type.register_alias "text/html", :facebook
Then you need to some way to recognize you’ve received a request from your Facebook app.
You could do a check on the params, as all requests from Facebook have a number of Facebook-specific parameters (fb_sig, etc, which is covered briefly in the second part of my Facebook lightweight library posts). This has the added advantage that you can use the normal URLs in your facebook templates/links. However, if you’re using REST-type routes — as I am — you may end up with difficulties for the moment, as all request from Facebook are POSTs. (According to FB, this may change in the future and already there’s a parameter in the request which says what method the original request was.)
A simpler way is to use the routes (or possibly a subdomain, as shown in the iPhone example). I’ve already got the Autopendium FB app set up so that all request from Facebook have a base URL of autopendium.com/facebook/ (i.e. what Facebook calls the callback URL) . This normally sends everything to the Facebook controller, so /facebook/latest goes to the #latest action in the FacebookController. However, if I add a couple of line to my routes.rb file:
map.connect 'facebook/:controller', :format => 'facebook', :action => 'index' map.connect 'facebook/:controller/:id', :format => 'facebook', :action => 'show'
… I get facebook/events (which is generated by a link in the Facebook canvas of apps.facebook.com/autopendium/events routing to the index action in the Events controller with our custom :facebook format. Likewise facebook/events/3 will route to the show action in the Events controller with an :id of 3 in the params hash.
Then in my Events controller, I just add an additional line to the respond_to block:
respond_to do |format| format.html # index.html.erb ... format.facebook # index.facebook.erb end
This means the response for the Facebook request will be served using a special facebook template, with no render :template => “special_facebook_template” needed.
Even better, if it will automatically use a custom facebook application layout if it exists (called application.facebook.erb). So all your standard links, frame, css can be included without a single extra line. Mine looks something like this:
<%= stylesheet_link_tag "facebook_basic" %> <fb:header decoration="add_border">Autopendium :: Stuff about old cars</fb:header> <br /> <fb:tabs> <fb:tab_item href="http://apps.facebook.com/autopendium" title="Intro">Intro</fb:tab_item> <fb:tab_item href="http://apps.facebook.com/autopendium/show" title="My Autopendium">My Autopendium</fb:tab_item> <fb:tab_item href="http://apps.facebook.com/autopendium/events" title="Classic Car Events">Classic Car Events</fb:tab_item> </fb:tabs> <div class="container"> <%= yield %> </div>