---
title: fist in your facebook
teaser: How to connect to Facebook applications.
tags: web,rails
author: Jared Carroll
published_on: 2007-06-14
---

Here at t-bot, we just finished porting a part of one of our existing Rails
applications to utilize the new Facebook platform.  It took about a week, and
despite the decent documentation for the Facebook platform, there was definitely
a lot of trial and error.  There also seems to not be a lot of resources on
using Rails and the Facebook platform, so we thought we'd give back what we
learned.

First, some basic info about the Facebook platform.

Basically Facebook acts like a middleman between the Facebook user and your
Rails app.  So when a Facebook user clicks on a link or submits a form in your
Facebook application, Facebook POSTs that information, along with some of its
own information, to your Rails application.  When you configure your Facebook
app settings, you tell Facebook that whenever it gets a request for, say,
'https://apps.facebook.com/your-facebookapp-name' to route that to
'https://www.your-railsapp.com'.

**EVERYTHING IS A POST**.

Remember this.

It's important because your RESTful routes aren't going to work.  You'll need
named routes in addition to your RESTful routes.

In `routes.rb`:

```ruby
ActionController::Routing::Routes.draw do |map|

  map.resources :users

  map.with_options :controller => 'users' do |m|
    m.facebook_users 'your-facebookapp-name/users', :action => 'index'
  end

end
```

Say we configured our Facebook app settings to route every request from a
Facebook user from 'https://apps.facebook.com/your-facebookapp-name' to
'https://www.your-railsapp.com'.

Now when a Facebook user goes to
'https://apps.facebook.com/your-facebookapp-name/users' Facebook is going to do a
POST to 'https://your-railsapp.com/users', which when using RESTful routes, will
get routed to users#create.  But you wanted '/users' to be a list of all users
available by a GET, just like a normal RESTful route.  So you'll need to add a
named route; that way you can POST to
'https://apps.facebook.com/your-facebookapp-name/users' and get routed to
users#index.

## Controller structure

1) Keep your same controllers, and add conditional logic to determine if the
request was from Facebook:

```ruby
class UsersController < ApplicationController

  def show
    @user = User.find params[:id]
    if facebook?
      render :template => 'show_fbml', :layout => 'facebook'
    end
  end

 private

  def facebook?
    ! params[:fb_sig].nil?
  end

end
```

In every request from Facebook, which is always a POST, Facebook will send some
of its own parameters.  One of those parameters is `fb_sig` which means the
request came from Facebook.  Since our views in the Facebook portion of the app
were different from the views in the non-Facebook portion of the app, we decided
to create separate views to keep things separate.  The above line:

```erb
render :template => 'show_fbml', :layout => 'facebook'
```

renders our Facebook 'show' page.  We adopted the convention of suffixing all
Facebook views with `_fbml` as well as using a separate layout specifically for
Facebook.  `fbml` stands for FaceBook Markup Language.  It's basically <abbr
title="HyperText Markup Language">HTML</abbr> plus a bunch of Facebook specific
tags such as:

```xml
    <fb:action href="new.php">Create a new photo album</fb:action>
```

which renders to a link.  We used mostly just <abbr title="HyperText Markup
Language">HTML</abbr> in our Facebook views with some <abbr title="Facebook
Markup Language">FBML</abbr> for the Facebook specific headers and navigation.
We adopted the `_fbml` file name suffix because when executing the following in
a view:

```erb
<%= render :partial => 'user', :object => @user %>
```

Rails will always look for a file in the current action's controller's views
directory named `_user.rhtml`.   The file it's looking for will always be
prefixed with an underscore and its file type will be '.rhtml'.  It doesn't
matter if you specify the file extension or not in the #render call.
Originally, the plan was to name all our Facebook views `<view-name>.fbml`.

You can take this a little farther to clean up your controllers some more and
make them look more RESTful.

```ruby
class UsersController < ApplicationController

  def show
    @user = User.find params[:id]
    respond_to do |wants|
      wants.html
      wants.fbml
    end
  end

end
```

And add the following in _config/environment.rb_ or a file in _lib_:

```ruby
Mime::Type.register 'text/html', :fbml
ActionController::MimeResponds::Responder::DEFAULT_BLOCKS[:fbml] = %(lambda {
  render :action => "\#{action_name}_fbml", :layout => 'facebook'
})
```

That adds a custom <abbr title="Multi-Purpose Internet Mail
Extension">MIME</abbr> type that we can then use in `#respond_to` blocks.

The `lambda` function in the string will be the default block that gets executed
when you write the following in an action:

```ruby
class UsersController < ApplicationController

  def new
    @user = User.new
    respond_to do |wants|
      wants.fbml
    end
  end

end
```

That's going to try to render a file named `new_fbml.rhtml` in
`app/views/users`.  By using `#respond_to`, we can get rid of the conditional
logic and `#facebook?` query method in our controller, which is nice.

In order for the `#respond_to` to work, you'll need to make sure all your urls
requested from Facebook end in `.fbml` or add a default `:format` parameter to
your named routes.  So you'll have to update your routes file:

In `routes.rb`:

```ruby
ActionController::Routing::Routes.draw do |map|
  map.with_options :controller => 'users' do |m|
    m.facebook_user 'your-facebookapp-name/user/:id.fbml',
      :action => 'show'
    # or
    m.facebook_user 'your-facebookapp-name/user/:id',
      :action => 'show',
      :format => 'fbml'
  end
end
```

You can write a functional test for the `fbml` <abbr title="Multi-Purpose
Internet Mail Extension">MIME</abbr> type like normal, but you'll need to
include the `:format` parameter on your POSTs:

```ruby
def test_should_find_the_user_with_the_given_id_on_POST_to_show_from_facebook
  post :show, :id => users(:one).id, :format => 'fbml'

  assert_equal users(:one), assigns(:user)
  assert_response :success
  assert_template 'show_fbml'
end
```

2) Use namespaced controllers such as:

```ruby
class Facebook::UsersController < ApplicationController
end
```

We started out this way in order to keep a nice separation between our Facebook
and non-Facebook parts of the app.  However, there was just too much duplication
in the controllers so we decided to use method #1 and put Facebook specific
conditional logic in our existing non-namespaced controllers.

## Views

Always make sure to use `:only_path => true` in all your named route calls like
so:

```erb
<% form_for :user, @user,
            :url => facebook_create_user_url(:only_path => true) do |form| -%>
<% end %>
```

All <abbr title="Uniform Resource Locator">URL</abbr>s should be relative, so
Facebook can append 'https://apps.facebook.com/' to each of them.  However,
Facebook will not append your Facebook application's unique path prefix (in the
above examples: 'your-facebookapp-name'), so your named routes will have to
include it:

In `routes.rb`:

```ruby
map.facebook_user 'your-facebookapp-name/user/:id', :action => 'show'
```

Now this works, but you'll probably want multiple environments for your Facebook
application.  So you have to create multiple Facebook applications, each with a
unique path prefix, and then externalize that path prefix in your Rails
environment specific files.  The above code then becomes:

In `routes.rb`:

```ruby
map.facebook_user "#{FACEBOOK_PATH_PREFIX}/user/:id", :action => 'show'
```

And in _config/environments/development.rb_

```ruby
FACEBOOK_PATH_PREFIX = 'your-facebookapp-name-development'
```

And in _config/environments/production.rb_

```ruby
FACEBOOK_PATH_PREFIX = 'your-facebookapp-name'
```

## Library

At first I'd thought to try write my own library to make all the Facebook <abbr
title="Application Programming Interface">API</abbr> calls, but then
reconsidered because of time constraints, to use the
[rFacebook](http://www.livelearncode.com/archives/14) API.  It doesn't feel very
Rubyish because it looks like basically a straight port of the <abbr title="PHP
HyperText Preprocessor">PHP</abbr> Facebook client.  It gives you a Facebook
session object that you can use to make Facebook <abbr title="Application
Programming Interface">API</abbr> calls pretty easily, like:

```ruby
class UsersController < ApplicationController

  # mix in the library
  include RFacebook::RailsControllerExtensions

  def show
    doc = fbsession.users_getInfo :uids => [fbsession.session_user_id],
          :fields => %w(first_name last_name)
    # doc is an Hpricot XML document
    @user = User.find :first,
           :conditions => [name = ?',
           "#{doc.at('first_name').inner_html} #{doc.at('last_name').inner_html}"]
  end

end
```

rFacebook uses \_why's [Hpricot](http://code.whytheluckystiff.net/hpricot/), so
make sure you got that gem unpacked in your 'vendor/plugins'.

## Exceptions

One exception we kept seeing in our logs was:

```ruby
RFacebook::FacebookSession::NotActivatedException
  (You must activate the session before using it.)
```

The way to 'activate' your session is to log into Facebook, go to your
application page, go to its 'About' page, click the 'Add Application' button and
then add the application to your list of applications.  This got rid of the
exception every time.

## Flash and Session

Since the Rails `flash` is associated with each Rails session, you can't use it
because Facebook does not pass the Rails cookie back on each request it proxies
to your Rails app.  Instead, each request from Facebook is seen as a brand new
session to your Rails app.  This also prevents you from using the `session`.

One solution here would be to write your own FacebookSession and somehow
configure Rails to use that instead of its default whenever you reference
`session` in a controller.  Facebook specific session information is passed
along from Facebook to your Rails app in every POST, which could be used as a
session key.

**update:** Moved :layout parameter from the 'fbml' content type respond_to
block to the 'fbml' custom <abbr title="Multi-Purpose Internet Mail
Extension">MIME</abbr> type's default block.
