Tutorial on developing a Facebook platform application with Ruby On Rails

June 29th, 2007

If you havn’t heard of Facebook. Where you been, living in a cave! Well you also probably know that in June 2007 Facebook opened a development platform for integrating new applications into Facebook, also codenamed F8. By the 24th June there were a 1,000 new applications available for the Facebook networkers to play with. According to both the online and traditional news media, Facebook have changed the game.

Well ZDNet are reporting that 1,000 developers a day are joining Facebook platform. That as it may, they are not all creating applications right away. The documentation is patchy, spread-out, difficult to understand and incomplete. It is a wonder so many applications are built because there is pain to go through to get there.

But this article is a complete tutorial for building a simple Facebook application in Ruby On Rails and should allow you to hit the ground running. We are going to create a recipes sharing application for Facebook from scratch.

UPDATE: The second part can be found here

If you havn’t heard of Facebook. Where you been, living in a cave! Well you also probably know that in June 2007 Facebook opened a development platform for integrating new applications into Facebook, also codenamed F8. By the 24th June there were a 1,000 new applications available for the Facebook networkers to play with. According to both the online and traditional news media, Facebook have changed the game.

Well ZDNet are reporting that 1,000 developers a day are joining Facebook platform. That as it may, they are not all creating applications right away. The documentation is patchy, spread-out, difficult to understand and incomplete. It is a wonder so many applications are built because there is pain to go through to get there.

But this article is a complete tutorial for building a simple Facebook application in Ruby On Rails and should allow you to hit the ground running. We are going to create a recipes sharing application for Facebook from scratch.

UPDATE: The second part can be found here

First of all some useful links:

Facebook has provided many different integration points and different APIs to an application so it is worth reading Anatomy of a Facebook Application first which will explain the areas an application can affect for a user.

There are two different types of Facebook application
  1. A desktop application
  2. A website application

We are only going to be concerned with website applications for now. There is also 3 different ways an application can interact with Facebook.

  1. As an external application but interacting with the remote API
  2. Running in an IFRAME on the Facebook site
  3. Running in a Facebook canvas and using Facebook Markup Language (FBML)

The application can use one or all of these techniques. We are going to try all three in this tutorial.

But first lets start by creating a Rails application with NO Facebook stuff and then we’ll look at FB-ing it. Originally I planned to do this completely RESTfully but it turns out it doesn’t work well with Facebook canvas, so it’s old-skool Rails i’m afraid.

Lets create the application:

    
rails socialrecipe

cd socialrecipe
    

And create some models. We are keeping this application very simple for demo purposes so just a recipe and a comment model needed. (you will need to concate these lines properly)

    
ruby script/generate model Recipe 
title:string summary:text ingredients:text instructions:text 
cooking_time:string created_at:datetime updated_at:datetime

ruby script/generate model Comment 
recipe_id:integer body:text created_at:datetime updated_at:datetime
    

Lets create our databases. I’m using MySQL

    
sudo mysqladmin create socialrecipe_development
sudo mysqladmin create socialrecipe_test
    

Lets changed to using database sessions. Now edit config/environment.rb change the Rails Initializer section and uncomment the following line

    
config.action_controller.session_store = :active_record_store
    

And add a line at the end of config/environment.rb

    
ActionController::AbstractRequest.relative_url_root = "/socialrecipe" 
    

What this does is change the Rails application to run starting with a socialrecipe in the first part of the URL. We need this for later but you will need your own unique name (i’m using socialrecipe). So think of one and take note.

Now lets create our development database.

    
rake db:sessions:create

rake db:migrate
    

Now lets create our controllers

    
ruby script/generate controller recipes 
ruby script/generate controller comments
    

Excellent. Now lets change our model definitions so that Recipes can have many comments. Edit app/models/recipe.rb to

    
class Recipe < ActiveRecord::Base
    has_many :comments

    validates_presence_of :title, :summary, :ingredients, :instructions
end
    

And edit models/comment.rb to

    
class Comment < ActiveRecord::Base
    belongs_to :recipe

    validates_presence_of :body, :recipe_id

end
    

Now lets add the controller for the recipe. Edit app/controllers/recipes_controller.rb and change it to

    
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.find(:all)
    @title = "All recipes" 
  end

  def show
    @recipe = Recipe.find(params[:id])
  end

  def new
    @recipe = Recipe.new
  end

  def edit
    @recipe = Recipe.find(params[:id])
  end

  def create
    @recipe = Recipe.new(params[:recipe])

    if @recipe.save
      flash[:notice] = 'Recipe was successfully created.'
      redirect_to :action => 'show', :id => @recipe
    else
      render :action => "new" 
    end

  end

  def update
    @recipe = Recipe.find(params[:id])

    if @recipe.update_attributes(params[:recipe])
      flash[:notice] = 'Recipe was successfully updated.'
      redirect_to :action => 'show', :id => @recipe
    else
      render :action => "edit" 
    end
  end

end
    

Edit app/controllers/comments_controller.rb and change it to

    
class CommentsController < ApplicationController

  def new
    @comment = Comment.new
    @comment.recipe_id = params[:recipe_id]
  end

  def create
    @comment = Comment.new(params[:comment])
    if @comment.save
      flash[:notice] = 'Comment was successfully created.'
      redirect_to :controller => 'recipes', :action => 'show', :id => @comment.recipe
    else
      render_action 'new' 
    end
  end
end
    

Pretty standard stuff so far creating simple controllers to create, read and update recipes and create comments. Lets setup the views

Add app/views/recipes/index.rhtml with

    
<h2><%= @title %></h2>

<%@recipes.each do |recipe|%>
<div>
<h3><%= link_to h(recipe.title), :controller => 'recipes', :action => 'show', :id => recipe%></h3>
<p><%=h recipe.summary %></p>
</div>
<%end%>

<br/>
<%= link_to 'New recipe', :controller => 'recipes', :action => 'new' %>
    

Add app/views/recipes/new.rhtml with

    
        <h1>New recipe</h1>

        <%= error_messages_for :recipe %>

        <% form_for(:recipe, :url => {:controller => 'recipes', :action => 'create'}) do |f| %>
          <dl>
              <dt><label for="recipe_title">Title:</label></dt>
            <dd><%= f.text_field :title %></dd>
            <dt><label for="recipe_summary">Summary:</label></dt>
            <dd><%= f.text_area :summary %></dd>
            <dt><label for="recipe_ingredients">Ingredients:</label></dt>
            <dd><%= f.text_area :ingredients %></dd>
            <dt><label for="recipe_instructions">Cooking Instructions:</label></dt>
            <dd><%= f.text_area :instructions %></dd>
            <dt><label for="recipe_cooking_time">Cooking Time:</label></dt>
            <dd><%= f.text_field :cooking_time %> mins</dd>
            <dt></dt>
            <dd><%= submit_tag "Create" %></dd>
          </dl>
        <% end %>

        <br/>
        <%= link_to 'Back', :controller => 'recipes' %>
    

The use of dl tags for forms is not a facebook thing. It’s just the way I rock it.

Add app/views/recipes/edit.rhtml with

    
<h1>Editing recipe</h1>

<%= error_messages_for :recipe %>

<% form_for(:recipe, 
      :url => {:controller =>'recipes', :action => 'update', :id => @recipe}) do |f| %>
<dl>
    <dt><label for="recipe_title">Title:</label></dt>
    <dd><%= f.text_field :title %></dd>
    <dt><label for="recipe_summary">Summary:</label></dt>
    <dd><%= f.text_area :summary %></dd>
    <dt><label for="recipe_ingredients">Ingredients:</label></dt>
    <dd><%= f.text_area :ingredients %></dd>
    <dt><label for="recipe_instructions">Cooking Instructions:</label></dt>
    <dd><%= f.text_area :instructions %></dd>
    <dt><label for="recipe_cooking_time">Cooking Time:</label></dt>
    <dd><%= f.text_field :cooking_time %> mins</dd>
    <dt></dt>
    <dd><%= submit_tag "Update" %></dd>
</dl>
<% end %>

<br/>
<%= link_to 'Show', :controller => 'recipes', :action => 'show', :id =>@recipe %> |
<%= link_to 'Back', :controller => 'recipes' %>
    

Add app/views/recipes/show.rhtml with

    
<h1><%=h @recipe.title %></h1>
<p><%=h @recipe.summary %></p>

<h2>Ingredients:</h2>
<p><%=h @recipe.ingredients %></p>

<h2>Instructions:</h2>
<p><%=h @recipe.instructions %></p>

<p><em>Cooking time is around <%=h @recipe.cooking_time %> mins</em></p>

<div id="comments">
    <h2>Displaying <%=pluralize(@recipe.comments.size, 'comment')%></h2>
    <%= link_to 'New comment', :controller => 'comments', :action => 'new', :recipe_id => @recipe %>
       <% @recipe.comments.each do |comment| %>
           <div>
                <p><%= comment.body %></p>    
                posted <%= time_ago_in_words(comment.created_at)  %> ago
            </div>
       <%end %>
</div>

<br/>
<%= link_to 'Edit', :controller => 'recipes', :action => 'edit', :id => @recipe %> |
<%= link_to 'Back', :controller => 'recipes' %>
    

Add app/views/comments/new.rhtml with

    
<h1>New comment</h1>

<%= error_messages_for :comment %>

<% form_for(:comment, :url => 
      {:controller => 'comments', :action => 'create'}) do |f| %>
<dl>
    <dt><label for="comment_body">Body Content:</label></dt>
    <dd><%= f.text_area :body %></dd>
    <dt></dt>
    <dd><%= submit_tag "Create" %></dd>
</dl>
    <%= f.hidden_field :recipe_id, :value => @comment.recipe_id %>
<% end %>
    

And lets create a layout at app/views/layouts/index.rhtml

    
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Recipes: <%= controller.action_name %></title>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>

<%= yield  %>

</body>
</html>
    
and add this line to the app/controllers/application.rb
    
layout 'index'
    

change config/routes.rb to have a default route

    
map.connect '', :controller => "recipes" 
    

and delete index.html from your public directory. That’s it. We now have a simple (and very ugly application). Fire it up with script/server and go to http://localhost:3000/socialrecipe

Lets facebook-it!! First of all we need to install the Ruby Gem RFacebook (this tutorial uses the version 0.6.2. This is a helper for interacting with the Facebook APIs. It will require you install Hpricot but make sure you pick the ruby version not the jruby one.

    
sudo gem install rfacebook -v 0.6.2        
    

Now you’ll need to log on to facebook (what do you mean you’re not on facebook!) and add the Developer application. Once you have done that press the big “Set Up New Application” button.

Now fill in the following fields

Application Name: Social Recipes (You will need to change this as i have done social recipes) Check the box for Check here to indicate that you have read and agree to the terms of the Facebook Platform. Callback Url: http://localhost:3000/socialrecipe/ (The end of this is the same as your relative_url_root) Canvas Page URL: http://apps.facebook.com/ socialrecipe (The field of this is the same as your relative_url_root) Check Use iframe for now Can your application be added on Facebook? Yes Post-Add URL: http://apps.facebook.com/socialrecipe/ (The end of this is the same as your relative_url_root)

That is all hit save and you should have a page listing your application with an API Key and a Secret. Make a note of your applications API Key and Secret

Now lets edit our rails application. Add the following to your ApplicationController in app/controllers/application.rb

    
before_filter :require_facebook_login, :set_user
include RFacebook::RailsControllerExtensions

def facebook_api_key
  return "ADD YOUR API KEY HERE" 
end

def facebook_api_secret
  return "ADD YOUR SECRET HERE" 
end

def finish_facebook_login
  redirect_to :controller => "recipes" 
end

def set_user
  @current_fb_user_id = fbsession.session_user_id
end
    

and add the following to your config/environment.rb

    
require "facebook_rails_controller_extensions" 
    
You’ll see you need to add your own key and own secret to this. The set_user filter is going to set a variable in the controller with the logged in facebook users id. We need this because we are going to store which users created which recipes and comments. To do this we need to add columns to our recipes and comments tables to store the id in. These must be BIGINT if you are using MySQL. Facebook has A LOT of users.
    
script/generate migration add_fb_user_ids
    

Now edit db/migrate/004_add_fb_user_ids.rb to:

    
class AddFbUserIds < ActiveRecord::Migration
  def self.up
      add_column :recipes, :fb_user_id, :integer
      add_column :comments, :fb_user_id, :integer

      #if mysql
      execute("alter table recipes modify fb_user_id bigint")
      execute("alter table comments modify fb_user_id bigint")
  end

  def self.down
      remove_column :recipes, :fb_user_id
      remove_column :comments, :fb_user_id
  end
end
    
    
rake db:migrate
    

We now need to store the facebook user ids in the tables so we’ll add a hidden field to the forms

Add the following to app/views/recipes/new.rhtml and app/views/comments/new.rhtml just before the end of the form

    
<%= f.hidden_field :fb_user_id, :value => @current_fb_user_id %>
    

We are also going to protect the edit recipe to only allow you to edit your own recipes in app/views/recipes/show.rhtml change

    
    <%= link_to 'Edit', :controller => 'recipes', :action => 'edit', :id => @recipe %> |
    
to
    
<%is_my_recipe? @recipe do %>
    <%= link_to 'Edit', :controller => 'recipes', :action => 'edit', :id => @recipe %> |
<%end%>
    

and add the following to app/helpers/recipes_helper.rb

    
def is_my_recipe?(recipe, &block)
  yield if recipe.fb_user_id.to_i == @current_fb_user_id.to_i
end
    

See how we cast the facebook user ids to ints. This makes the match actually possible.

Now that was easy but it’s not very Facebooky. Where’s the social? Lets add some friends stuff.

Edit app/controllers/recipes_controller.rb and add the following actions

    
def my
  @title = "My recipes" 
  @recipes = Recipe.find_my_recipes(@current_fb_user_id)

  render :file => "recipes/index", :use_full_path => true
end

def friends
  @title = "Friends recipes" 

  xml_friends_get = fbsession.friends_get
  friend_ids = xml_friends_get.search("//uid").map{|uidNode| uidNode.inner_html.to_i}

  @recipes = Recipe.find_friends_recipes(friend_ids)
  render :file => "recipes/index", :use_full_path => true
end
    

Add the following to your Recipe model at app/models/recipe.rb

    
class << self

def find_my_recipes(fb_user_id)
  find(:all, :conditions => ['fb_user_id = ?', fb_user_id])
end

def find_friends_recipes(friends_fb_ids)
  find(:all, :conditions => ['fb_user_id IN (?)', friends_fb_ids])
end

end
    

You can see how we use the facebook API through the rfacebook fbsession.friends_get. This then returns Hpricot XML which we parse to get all the friends of the current user and then look for any Recipes they have created. lets just add these actions to some basic navigation.

Edit app/views/layouts/index.rhtml and include the following just under the body tag

    
    <div id="navigation">
<%= link_to 'All recipes', :controller=>'recipes'%> | 
<%= link_to 'My recipes', :controller=>'recipes', :action => 'my'%> | 
<%= link_to 'Friends recipes', :controller=>'recipes', :action => 'friends'%>
    </div>
    

We’re done lets start with script/server and navigate to http://localhost:3000/socialrecipe. Woah did you just get redirected to Facebook. Oh yes! It should be asking you to add your new Facebook application now. Tick keep me logged in and press the Log in to button.

You should now get returned to your local application. This is an example of an External Facebook Application. Try creating a new recipe and check it appears in My Recipes.

Try going to http://apps.facebook.com/socialrecipe/. You should see your application inside Facebook. This is an example of an IFrame Facebook Application.

Your application could now be ready to go, you could deploy your application to a server, submit it to the directory and let people add it. But its still not very facebook’y. We shall now go on and look at turning this into a Canvas application. In a canvas FBML application, instead of embedding your application in an IFrame, Facebook is going to make a proxy request to your application and parse the resulting markup. In this markup it can contain the Facebook Markup Language which can display more Facebook like interfaces and other direct interactions but it does limit you in what markup you can use and what you can achieve.

Now because Facebook needs to make a request to a server it must be accessible to Facebook, hence you can’t just run it on your laptop (or you can if you have a static Ip you could route facebook to). But i’m going to use my excellent RailsMachine serving to deploy the application at http://socialrecipe.liverail.net/. So don’t attempt these next steps unless you can also deploy your server to the world.

First of all we need to switch our layouts if we are in the canvas. So change the app/controllers/application.rb
    
layout 'index'
    

to

    
layout :check_fb_layout

def check_fb_layout
  if in_facebook_canvas?
    "index_fbml" 
  else
    "index" 
  end
end
    

This is going to check if the request is in a facebook canvas and if so render a different layout. So add a new layout at app/views/layouts/index_fbml.rhtml

    
<fb:dashboard>
  <fb:header decoration="add_border" icon="false">Liverail Recipes</fb:header>
  <fb:create-button 
href="<%= url_for(:controller => 'recipes', :action => "new", :only_path => true) %>">
New Recipe</fb:create-button>    
</fb:dashboard>
<fb:tabs>
  <fb:tab_item 
href="http://apps.facebook.com<%= url_for(:controller => 'recipes', :action => "index", :only_path => true) %>" 
title="All Recipes"></fb:tab_item>
  <fb:tab_item 
href="http://apps.facebook.com<%= url_for(:controller => 'recipes', :action => "my", :only_path => true) %>" 
title="My Recipes">My Recipes</fb:tab_item>
  <fb:tab_item 
href="http://apps.facebook.com<%= url_for(:controller => 'recipes', :action => "friends", :only_path => true) %>" 
title="Friends Recipes">Friends Recipes</fb:tab_item>
</fb:tabs>

<%unless flash[:notice].blank? %>
<fb:status><%= flash[:notice] %></fb:status>
<%end%>

<%= yield  %>
    

This will add a Facebook navigation tabs. Notice how the fb:create-button_ works fine with a relative URL but the fb:tab_item requires an absolute url with the http://apps.facebook.com, this is one of the many oddities with FBML. Additionaly we must surround the fb:status with a check because it won’t work if you have a blank tag.

We now need to render different markup for our pages.

Add the following to app/controllers/application.rb

    
      def render_facebook(template = default_template_name)
        if in_facebook_canvas? #or true
           render :action => "#{template}_fbml" 
        else
           render :action => "#{template}" 
        end
      end
    

You now need to add a call to render_facebook at the end of actions index, show, new, edit for the recipes controller, add a call to render_facebook “index” at the end of actions my, friends for the recipes controller and finally render_facebook at the end of actions new for the comments controller.

Lets create some FBML rendering templates.

First app/views/recipes/index_fbml.rhtml

    
        <fb:header decoration="add_border" icon="false"><%= @title %></fb:header>
        <%@recipes.each do |recipe| %>
            <div>
            <h3><%= link_to h(recipe.title), :controller => 'recipes', :action => 'show', :id => recipe%></h3>
            <p>
                      <fb:profile-pic uid="<%= recipe.fb_user_id %>" size="thumb" />
                      <fb:name uid="<%= recipe.fb_user_id %>" ifcantsee="anonymous" />
                       </p>
            <p><%=h recipe.summary %></p>
            </div>
        <%end%>
    

app/views/recipes/new_fbml.rhtml

    
        <fb:header decoration="add_border" icon="false">New Recipe</fb:header>
        <%unless @recipe.errors.blank?%>
        <fb:error><%= error_messages_for :recipe %></fb:error>
        <%end%>

            <% form_for(:recipe, :url => {:controller => 'recipes', :action => 'create'}) do |f| %>
              <dl>
                  <dt><label for="recipe_title">Title:</label></dt>
                <dd><%= f.text_field :title %></dd>
                <dt><label for="recipe_summary">Summary:</label></dt>
                <dd><%= f.text_area :summary %></dd>
                <dt><label for="recipe_ingredients">Ingredients:</label></dt>
                <dd><%= f.text_area :ingredients %></dd>
                <dt><label for="recipe_instructions">Cooking Instructions:</label></dt>
                <dd><%= f.text_area :instructions %></dd>
                <dt><label for="recipe_cooking_time">Cooking Time:</label></dt>
                <dd><%= f.text_field :cooking_time %> mins</dd>
                <dt></dt>
                <dd><%= submit_tag "Create" %></dd>
              </dl>
            <%= f.hidden_field :fb_user_id, :value => @current_fb_user_id %>
            <% end %>

        <br/>
        <%= link_to 'Back', :controller => 'recipes' %>
    

app/views/recipes/edit_fbml.rhtml

    
        <fb:header decoration="add_border" icon="false">Edit Recipe</fb:header>
        <%unless @recipe.errors.blank?%>
        <fb:error><%= error_messages_for :recipe %></fb:error>
        <%end%>

        <% form_for(:recipe, :url => {:controller =>'recipes', :action => 'update', :id => @recipe}) do |f| %>
        <dl>
            <dt><label for="recipe_title">Title:</label></dt>
            <dd><%= f.text_field :title %></dd>
            <dt><label for="recipe_summary">Summary:</label></dt>
            <dd><%= f.text_area :summary %></dd>
            <dt><label for="recipe_ingredients">Ingredients:</label></dt>
            <dd><%= f.text_area :ingredients %></dd>
            <dt><label for="recipe_instructions">Cooking Instructions:</label></dt>
            <dd><%= f.text_area :instructions %></dd>
            <dt><label for="recipe_cooking_time">Cooking Time:</label></dt>
            <dd><%= f.text_field :cooking_time %> mins</dd>
            <dt></dt>
            <dd><%= submit_tag "Update" %></dd>
        </dl>
        <% end %>

        <br/>
        <%= link_to 'Show', :controller => 'recipes', :action => 'show', :id =>@recipe %> |
        <%= link_to 'Back', :controller => 'recipes' %>
    

app/views/recipes/show_fbml.rhtml

    
<fb:header decoration="add_border" icon="false">
<%=h @recipe.title %>
</fb:header>
<fb:profile-pic uid="<%= @recipe.fb_user_id %>" size="normal" /> 
<fb:name uid="<%= @recipe.fb_user_id %>" ifcantsee="anonymous" />
<p><%=h @recipe.summary %></p>

<h2>Ingredients:</h2>
<p><%=h @recipe.ingredients %></p>

<h2>Instructions:</h2>
<p><%=h @recipe.instructions %></p>

<p><em>Cooking time is around <%=h @recipe.cooking_time %> mins</em></p>

<div id="comments">
    <h2>Displaying <%=pluralize(@recipe.comments.size, 'comment')%></h2>
    <%= link_to 'New comment', :controller => 'comments', :action => 'new', :recipe_id => @recipe %>
       <% @recipe.comments.each do |comment| %>
           <div>
                <fb:profile-pic uid="<%= comment.fb_user_id %>" size="thumb" />
            <p><%= comment.body %></p>    
                posted <%= time_ago_in_words(comment.created_at)  %> 
ago by 
<strong>
<fb:name uid="<%= comment.fb_user_id %>" ifcantsee="anonymous" />
</strong>
            </div>
       <%end %>
</div>

<br/>
<%is_my_recipe? @recipe do %>
    <%= link_to 'Edit', :controller => 'recipes', :action => 'edit', :id => @recipe %> |
<%end%>
<%= link_to 'Back', :controller => 'recipes' %>
    

app/views/comments/new_fbml.rhtml

    
<fb:header decoration="add_border" icon="false">New Comment</fb:header>
<%unless @comment.errors.blank?%>
<fb:error><%= error_messages_for :comment %></fb:error>
<%end%>

<% form_for(:comment, :url => {:controller => 'comments', :action => 'create'}) do |f| %>
<dl>
    <dt><label for="comment_body">Body Content:</label></dt>
    <dd><%= f.text_area :body %></dd>
    <dt></dt>
    <dd><%= submit_tag "Create" %></dd>
</dl>
    <%= f.hidden_field :recipe_id, :value => @comment.recipe_id %>
    <%= f.hidden_field :fb_user_id, :value => @current_fb_user_id %>
<% end %>

<br/>
<%= link_to 'Back', :controller => 'recipes', :action => 'show', :id => @comment.recipe_id %>
    

Now i’m all done. I’m deploying my application to socialrecipe.liverail.net using capistrano and their great 5 min app deployment but you use whichever method you do to deploy your application (remember to install the rfacebook gem on your server). Once that is done you need to change the application settings in Facebook. Log on to Facebook and go the Developer application > See my apps > Edit Settings. Now change the Callback URL from http://localhost:3000/socialrecipe/ to http://socialrecipe.liverail.net/socialrecipe/ with your own names of course. Now click the Use FBML radio button and save

Go to http://apps.facebook.com/socialrecipe. Done! Ruby on Rails Facebook application.

It isn’t the best looking but you can style to your heart’s content. Go to here if you want to add my demo application and play it. In the next part of this tutorial we can do some cool stuff with events and profiles.

UPDATE: The second part can be found here

42 Responses to “Tutorial on developing a Facebook platform application with Ruby On Rails”

  1. Alex MacCaw Says:

    Great article, have you seen the one that thoughtbot did: http://giantrobots.thoughtbot.com/2007/6/14/fist-in-your-facebook

  2. David Fitzgibbon Says:

    Great tutorial, been doing a bit of experimenting with it myself lately. Not sure if its any use to you but in my app I’ve created a FacebookUser class, with a handy method called create_from_fbsession, which also creates a friends array of FacebookUsers. So you can do things like…

    @current_user = FacebookUser.create_from_fbsession(fbsession)

    Then in your view you could do…

    Welcome <%= @current_user.name %>, your friends are…
    <% @current_user.friends.each do |user| %> <%= user.name %> <% end %>

    Here’s the code…

    class FacebookUser

    def self.fields
      [:uid,:first_name,:last_name,:name,:pic_small,:pic_big,:pic_square,:pic,
       :affiliations,:profile_update_time,:timezone,:religion,:birthday,:sex,
       :hometown_location,:meeting_sex,:meeting_for,:relationship_status,
       :significant_other_id,:political,:current_location,:activities,:interests,
       :is_app_user,:music,:tv,:movies,:books,:quotes,:about_me,:hs_info,
       :education_history,:work_history,:notes_count,:wall_count,:status,:has_added_app]
    end
    fields.each {|f| attr_accessor f}
    attr_accessor :friends
    def attributes
      attrs = { }
      self.class.fields.each{|f| attrs[f] = send(f)}
      attrs
    end
    def status=(status_xml)
      xml = Hpricot(status_xml).at("message")
      @status = xml.inner_html if xml
    end
    def self.create_from_xml(xml)
      fb_user = new
      fields.each do |k|
        fb_user.send(k.to_s + "=", xml.at(k.to_s).inner_html)
      end
      fb_user
    end
    def self.create_from_fbsession(fbsession, get_friends=true)
      xml = fbsession.users_getInfo(:uids => [fbsession.session_user_id], :fields => fields)
      fb_user = create_from_xml(xml)
      friends_uids = (fbsession.friends_get/:uid).map{|u| u.inner_html}
      friends_xml = fbsession.users_getInfo(:uids => [friends_uids], :fields => fields)
      fb_user.friends = (friends_xml/:user).map{|xml| create_from_xml(xml)}
      fb_user
    end

    end

    I hope it displays ok, if not I can email it to you instead.

  3. Stuart Eccles Says:

    Yes i’ve done something similar, although your implementation looks better (although i would go with FacebookUser.new(fbsession)). I didn’t want to abstract the implementation too heavily in the tutorial to make it obvious what is going on.

    Thoughtbots post is also cool, i hadn’t seen it before but its not quite complete. I very much like this idea
    
    Mime::Type.register 'text/html', :fbml
    
      ActionController::MimeResponds::Responder::DEFAULT_BLOCKS[:fbml] = %(lambda { render :action => "\#{action_name}_fbml",                                                                                                                                                           :layout => 'facebook' }
    
    
  4. fs Says:

    I’m having a problem when I add the: include RFacebook::RailsControllerExtensions to the application.rb

    I get: Status: 500 Internal Server Error Content-Type: text/html

    any ideas?

  5. Stuart Eccles Says:

    Have you install the rfacebook gem and added require “facebook_rails_controller_extensions” to your environment.rb

  6. fs Says:

    yes on both accounts.

    when I run: gem search—local rfacebook I get: rfacebook (0.6.2)

    and near the top of my environment.rb, I have this, copying 3 lines:

    1. Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(FILE), ‘boot’) require “facebook_rails_controller_extensions”
  7. fs Says:

    my error

  8. fs Says:

    the last like as wrong, you can delete that post

    my error this time

  9. Stuart Eccles Says:

    how about the first few lines of your stack trace

  10. fs Says:

    I can’t explain it Stuart, I hit F5 on the page this morning when I woke up and it redirected to facebook to log in, the came back to the page and started working properly. I’ve spent the last 45 minutes to try and recreate the error again but can’t. Even though I couldn’t even get it to work over the last couple days, trying everything. Maybe something propogated, I don’t know. bizarre. Thanks for your help though. I’ll continue on with the tutorial Thanks!

    ps. By stack trace do you mean entries from the development.log, or is there a way to get the stack trace while in the interactive debugger (rdebug)?

  11. fs Says:

    I’m not familiar with this syntax: class << self

    and I get a runtime error with it in, is this a typo in your tutorial? thanks

  12. Russ D'Sa Says:

    fs: class << self is used in this context to add class methods to the Recipe model. It is one of the few ways to add class methods in Ruby (and do some other devious things I won’t get into). No typo.

  13. Readmore Says:

    I’ve having the same problem as fs above. I get an 500 -Internal Server Error and the stack trace leads back to require “facebook_rails_controller_extensions”. Does anyone have any idea what that is about?

  14. Thanos Says:

    Hello,

    I have followed your steps, but however I have a problem when I go to http://localhost:3000/socialrecipe before install the rfacebook gem. I have this message in my firefox:

    Not Found `/socialrecipe/’ not found.

    Do you have any idea?

    Thank you

  15. Stuart Eccles Says:

    have you added a default route map.connect ’’, :controller => “recipes”

    and added this to the end of the environment.rb ActionController::AbstractRequest.relative_url_root = ”/socialrecipe”

    NB When you are integrating with facebook this relativeroute is going to have to be unique for your application

  16. Thanos Says:

    I have solved my problem (rails dependencies).

  17. P Says:

    Excellent tutorial, but very frustrating: the code works on my Mac, but not on my Ubuntu slice. I’m using the latest rubygems and I installed the correct rfacebook from scratch, but to no avail. :(

  18. mushy Says:

    thx Stuart, great tutorial, had few problems like ERROR 100: Invalid parameter (”auth.getSession”, {:auth_token=>”asd″, :v=>”1.0″, :api_key=>”xyz″, :method=>”facebook.auth.getSession”, :sig=>”qwe”}), have solved this issue by using rfacebook-0.6.1 gem, i was using rfacebook-0.6.3 earlier, thx again nice work

  19. cow orker Says:

    I’ve not done the tut but I will set aside a few hours on the weekend to give it a go.

    Thanks for this

  20. Tomasz Gorski Says:

    Brilliant idea Stuart. Thanks for very interesting article. btw. I really enjoyed reading all of your articles. It’s interesting to read ideas, and observations from someone else’s point of view… makes you think more.

  21. mushy Says:

    hey guys,

    i added AbstractRequest.relative_url_root = ”/myapp” in environment.rb file, but when i run the url , http://localhost:3000/myapp/accounts/login iam getting the login page but its not showing any css stuff and images, in the development log iam getting, ActionController::RoutingError (no route found to match ”/stylesheets/login.css” with {:method=>:get})

    any suggestions?

    thx in advance

  22. Stuart Eccles Says:

    Yes i know this problem. with the relative root is added to the start of all urls even javascript and css ones!!. The only solution I know is to create a directory ‘myapp’ in your case under the public directory and then to copy the stylesheets and css directorys under myapp.

  23. mushy Says:

    thx Stuart, actually i had solved the problem same way u have suggested but not sure is this a correct way?

  24. Neil Says:

    Hi Stuart – rockin’ tutorial. Keep ‘em coming!

    I’m stuck with the following (appears when clicking on ‘Friends Recipes’ in or out of an iFrame);

    TypeError in RecipesController#friends can't dup NilClass RAILS_ROOT: script/../config/.. Application Trace | Framework Trace | Full Trace /usr/local/lib/ruby/gems/1.8/gems/rfacebook-0.6.3/lib/facebook_session.rb:157:in `dup' /usr/local/lib/ruby/gems/1.8/gems/rfacebook-0.6.3/lib/facebook_session.rb:157:in `call_method' /usr/local/lib/ruby/gems/1.8/gems/rfacebook-0.6.3/lib/facebook_session.rb:138:in `method_missing' app/controllers/recipes_controller.rb:53:in `friends'
  25. uday pratap Says:

    i was working on your tutorial and got every thing on good way every data is going to database and retrieving of data are also fine but when i didn’t filled any data while creating new recipe and clicked on the save button and was expecting error message i got this error

    Errors while loading page from application Runtime errors:

    fb:error: You must specify a message with an fb:message tag or a message attribute

    There are still a few kinks Facebook and the makers of social item are trying to iron out. We appreciate your patience as we try to fix these issues. Your problem has been logged – if it persists, please come back in a few days. Thanks!

    please help me to proceed and tell me the exact problem where i went wrong

    Thanks and regard

  26. Neil Says:

    Just to follow-up my problem with ‘dup NilClass’ – it looks like there’s a problem with the rfacebook gem, and it’s detailed here;

    http://facebook.jdg.net/forums/RFacebook-Help/topics/Anybody-else-getting-can-t-dup-NilClass-

    I still haven’t worked out how to apply the patch though!

  27. dshivhare Says:

    Great article. You have done a mistake is that you have written “and finally render_facebook at the end of actions new for the recipes controller.” but it should be “and finally render_facebook at the end of actions new for the comments controller.”

    Thanks

  28. Mo® Says:

    Great post. Thanks for putting it up.

    Is it possible for an existing Rails app to also be a Facebook app int hat it can be served through a browser or through Facebook? Mostly due to this:

    :require_facebook_login

    It doesn’t sound like it is possible, even if you take out an user related functions. I’m new to Rails so I may be missing something obvious.

  29. Hadel4 Says:

    I think i did this right but when i get to face book i get “Received HTTP error code 404 while loading” any ideas?

  30. Stuart Eccles Says:

    Yes it is possible to add a normal Rails application to also be a Facebook application. Have a look at thoughtbots post which is on that exact thing http://giantrobots.thoughtbot.com/2007/6/14/fist-in-your-facebook

  31. Mo® Says:

    Thanks for the quick reply. I see now you can do a

    if facebook?

    call which is pretty much what I am looking for. It still doesn’t look like cross-user interaction is possible, wherein someone can log into my app either through facebook or through a regular web browser. Maybe an extension to my :require_login is possible. I’ll work on it.

    Thanks for your help.

  32. Rosk Says:

    I am using Instant Rails and I get stuck on creating the database “sudo mysqladmin create social_recipe development” and i get the error ”’sudo’, is not recognized as an internal or external command, operable program or batch file” I have no idea what to do. Help would be great. Thanks

  33. Rosk Says:

    and i know its “socialrecipe_development” instead of “social_recipe development”...that was just a typo

  34. am Says:

    Thanks Stuart – excellent tutorial. I had the same problems as fs above and sorted them by uninstalling version 0.6.4 of rfacebook and replacing with 0.6.4 – ie

    gem uninstall -v 0.6.4 rfacebook gem install -v 0.6.2 rfacebook

    Hope this helps anyone else with these problems.

  35. jesse Says:

    I’m using the latest version of rfacebook (1.6.7) and was getting nill errors unrelated to any other posts here.

    This is documented in the trac @ http://rubyforge.org/tracker/index.php?func=detail&aid=12381&group_id=3607&atid=13796

    I changed the particular lines in the code on my local machine and it works fine now.

  36. ronb Says:

    This is a great article. Does anyone have any further information on how to make rails sessions work with Facebook. I have read on a different site that Facebook creates a new session with every request, but you can set up your configuration to store the Facebook session id in the sessions database for rails. So far I have been unable to get this to work. Any ideas?

    Thanks, Ron

    The article has you add the following to the end of your enviroments.rb

    ActionController::Base.session_options[‘session_key’] = ‘fb_sig_session_key’

    class CGI class Session class ActiveRecordStore def initialize(session, option = nil) session_id = session.session_id unless session = ActiveRecord::Base.silence { @session_class.find_by_session_id(session_id) } session = @session_class.new(:session_id => session_id, :data => {}) end end end end end

  37. trnsfmr Says:

    Great tutorial!

    But I get an error ( NoMethodError in RecipesController#index ) with the line: before_filter :require_facebook_login, :set_user

    I think these are the last three lines of the stack trace:

    /usr/local/lib/ruby/gems/1.8/gems/rfacebook-0.6.8/lib/facebook_rails_controller_extensions.rb:37:in `fbparams’ /usr/local/lib/ruby/gems/1.8/gems/rfacebook-0.6.8/lib/facebook_rails_controller_extensions.rb:60:in `fbsession’ /usr/local/lib/ruby/gems/1.8/gems/rfacebook-0.6.8/lib/facebook_rails_controller_extensions.rb:130:in `require_facebook_login’

    The issues seems to be around the facebook gem log-in, but I’m pretty sure I’ve added the neccassary include in environment.rb correctly.

  38. trnsfrmr Says:

    I fixed my issue by downgrading the rfacebook gem to version 0.6.3

  39. Chazz Scarfiotti Says:

    I have a couple ideas that I’d like to get onto Facebook, but I need help. Anybody available for freelance work? I don’t have a huge budget, but perhaps we can work something out that would make it a win-win.

    Write me “Chazz Scarfiotti” on Facebook!

    Cheers,

    Chazz

  40. Matt Savarino Says:
    $ gem install hpricot 3. hpricot 0.6 (ruby) $ gem install rfacebook Successfully installed rfacebook-0.7.0 $ vi config/environment.rb $ vi app/controllers/application.rb

    Application error Rails application failed to start properly

    $ gem uninstall rfacebook 5. All versions gem install -v 0.6.3 rfacebook

    Application error Rails application failed to start properly

    Any ideas why this isn’t working? Same problem “fs” and others.

  41. Matt Says:

    Post format doesn’t allow code tag, so reposting…

    $ gem install hpricot 3. hpricot 0.6 (ruby)

    $ gem install rfacebook Successfully installed rfacebook-0.7.0

    $ vi config/environment.rb $ vi app/controllers/application.rb

    Application error Rails application failed to start properly

    $ gem uninstall rfacebook 5. All versions

    $ gem install -v 0.6.3 rfacebook

    Application error Rails application failed to start properly

    Any ideas why this isn’t working? Same problem “fs” and others.

  42. Julian Says:

    @Rosk—sudo is a Mac / Unix thing; you don’t need to do that. Create your database using PHPmyAdmin directly.

Sorry, comments are closed for this article.