Continuing Facebook Applications with Ruby On Rails

July 16th, 2007

This is the continuing tutorial from the last post Tutorial on developing a Facebook platform application with Ruby On Rails where we can take the social recipe application and add some more Facebook features including posting to the feed, profile boxes, profile actions, the Facebook Query Language and sending out invitations to a user’s friends to come join in the cooking fun (oh boy). So lets get down to it, I’m presuming you have been through the first tutorial and have a working Facebook application. Even if you havn’t got it deployed today’s examples will work with an IFRAME application also.

UPDATE: This article is a bit out of date now, I have now posted an article on Rails, resful_authentication and Facebook connect

This is the continuing tutorial from the last post Tutorial on developing a Facebook platform application with Ruby On Rails where we can take the social recipe application and add some more Facebook features including posting to the feed, profile boxes, profile actions, the Facebook Query Language and sending out invitations to a user’s friends to come join in the cooking fun (oh boy). So lets get down to it, I’m presuming you have been through the first tutorial and have a working Facebook application. Even if you havn’t got it deployed today’s examples will work with an IFRAME application also.

Before we get into it Evan Weaver has posted two really useful things for Facebook and Ruby on Rails. First a way to output the Facebook exceptions nicely when viewing in Facebook and Second how to develop an application locally while tunneling from a server Facebook can see.

Sending events to the feed.

One of the features that makes Facebook so great is the powerful News Feed’s and mini-feed which keeps you updated on what’s going on with your friend’s lifes and in-touch with your application content. The Facebook API allows you to do two things with the feed. You can publish a News Feed story to just the user logged in with

    
fbsession.feed_publishStoryToUser(:title => "", :body => "", :image_1 => "")
    

or publish a Mini-Feed story to the user and a News Feed story to their friends with

    
fbsession.feed_publishActionOfUser(:title => "", :body => "", :image_1 => "")
    

In both cases you can publish up to four images in the story with will be strung to a 75×75 square by Facebook and cached and served by them. Additionally the stories may not show up depending on the quality of the other competing stories in the feed! Applications are also limited to calling feed_publishStoryToUser once every 12 hours for each user and feed_publishActionOfUser to 10 times in 48 hours for each user.

Now to our application. We are going to notify the user and all their friends when they create a new recipe so all their friends can go check it out and cook it. So in recipes_controller and the create action.

    
if @recipe.save

  fbsession.feed_publishActionOfUser(:title => "<fb:name/> 
  has added a recipe for <a href=\"http://apps.facebook.com#{url_for(:controller => 'recipes', :action => 'show', :id => @recipe, :only_path => true)}\" >#{@recipe.title}</a>", 
   :body => "#{@recipe.summary}")
    

In this case we use a url_for to add the URL to an absolute path. This is necessary for Facebook as all links on the pages outside your application must be absolute. We also have used a little FBML to display the user’s name in the news feeds.

Now it would be great if we could use observations on ActiveRecord models to do this but the fbsession is available only in the controllers, a limit of rfacebook

Adding a profile box to a user’s profile page

The profile box is a little mini-view of your application which is on a user’s profile page The quirk behind profile boxes is the don’t send a request to your application to display the content. Instead you must set what content (in FBML) will appear in your application box for each user. This is a push method rather than a pull method. More information here.

So when do you actually set the profile box content. Well it depends what you are putting in the box, if you are just updating it with information about the users data then you may want to update that users profile box after a certain event. If you have information from a users friends data you may need to update a users profile box and update any of their friends after a certain event.

In our case the profile box is going to contain just a list of the recipes a user has added, but we are going to keep it keep generic. Try adding this to the ApplicationController

    
def update_profile(uid = fbsession.session_user_id)
  #find the user specific recipes
  recipes = Recipe.find_my_recipes(uid)

  #render a partial to a string
  profile_box = render_to_string(:partial => 'users/profile_fbml', :locals => { :uid => uid, :recipes => recipes })

  #send the profile FBML to Facebook through the API for this user
  fbsession.profile_setFBML({:markup => profile_box, :uid => uid})
end
    

We can then call update_profile from any controller to update a users profile, by default it will update the current user but we can override this to say update a friends profile.

For the partail we need to create app/views/users/_profile_fbml.rhtml

    
<fb:if-is-own-profile>
  <fb:subtitle>You have created <%=pluralize(recipes.size, 'recipe')%></fb:subtitle>
  <fb:else>
   <fb:subtitle>
    <fb:name uid="<%=uid%>"/> has created <%=pluralize(recipes.size, 'recipe')%>
   </fb:subtitle>
  </fb:else>
</fb:if-is-own-profile>
<%recipes.each do |recipe| %>
  <div>
    <h2><a href="http://apps.facebook.com<%= url_for :controller => 'recipes', :action => 'show', :id => recipe%>">
    <%=h(recipe.title)%></a></h2>
    <p><%=h recipe.summary %></p>
  </div>
<%end%>

<fb:if-is-app-user>
<fb:else><a href="http://www.facebook.com/add.php?api_key=28e9f67df073c92829aa003e6762e074">
    Add Social Recipe for yourself</a></fb:else>
</fb:if-is-app-user>
    

This FBML code works in two ways, first if the user is viewing there own profile it says You have created but if someone else if viewing their profile it says Stuart Eccles has created for instance. We then link to the recipes, note the use of absolute URLs, a must on the profile box. Finally if another user has not added the application we chuck in a sneaky plug to our application. Note the api_key you will want to move this to a constant.

Finally we need to call the update_user and we will do so after a create or delete action on a controller i.e.

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

  if @recipe.update_attributes(params[:recipe])
   flash[:notice] = 'Recipe was successfully updated.'

   update_profile
   redirect_to :action => 'show', :id => @recipe
  else
   render :action => "edit" 
  end
end
    

Adding a profile action to a user’s profile page

A profile action appears as a link just under a users picture on their profile. The profile action link should be set at the same time as the profile box so to add a link we just need to add a bit of FBML to the end of the _profile_fbml.rhtml

    
<fb:if-is-own-profile>
<fb:profile-action url="http://apps.facebook.com<%= url_for :controller => 'recipes', :action => 'my'%>">
View My Recipes (<%=recipes.size%>)
</fb:profile-action>
<fb:else>
<fb:profile-action url="http://apps.facebook.com<%= url_for :controller => 'recipes', :action => 'index'%>">
View <fb:pronoun uid="<%=uid%>" possessive="true" capitalize="true"/> Recipes (<%=recipes.size%>)
</fb:else>
</fb:profile-action>
</fb:if-is-own-profile>
    

Again it gets switched depending on if the user is viewing their profile or someone else. If it is someone else we use the fb:pronoun to output His or Her.

Adding an application invitation

Our application is so cool no your users are going to want to share it with their friends (well yours will be, this tutorial application lacks coolness). So lets allow users to invite their friends.

We only want people to be inviting their friends how havn’t already added the application, unfortunately we don’t have an API call for this. Fortunately there is a way around this but we have to use the Facebook Query Language which is just like a select SQL statement. We can select data from the user, friend, group, group_member, event, event_member, photo, album, photo_tag tables. We can then call the API through fql_query method on fbsession, this will return XML we can then parse. You can try out your FQL first at the test console.

We need to create an invites controller:

    
script/generate controller invites         
    
    
class InvitesController < ApplicationController

  def select
    fql =  "SELECT uid, name FROM user WHERE uid IN" +
    "(SELECT uid2 FROM friend WHERE uid1 = #{@current_fb_user_id}) " +
    "AND has_added_app = 0" 
    xml_friends = fbsession.fql_query :query => fql
    @friends = Hash.new
    xml_friends.search("//user").map do|usrNode| 
      @friends[(usrNode/"uid").inner_html] = (usrNode/"name").inner_html
    end
    render_facebook
  end

end
    

In this code we select friends of the current user where the friends has_added_app = 0 so therefore are not using the application. We then parse their names and uids into a hash. Lets create two outputs for this list, a normal HTML one and an FBML one.

app/views/invites/select.rhtml

    
<% form_tag :action => 'send_invites' do -%>
<ul>
<% @friends.each do |uid,name|%>
     <li><input type="checkbox" name="friends[]" value="<%=uid%>"/> <%=name%></li>
<%end%>
</ul>

You can only invite a maximum of ten people at once.
<input type="submit" value="Invite"/>
<% end -%>
    
app/views/invites/select_fbml.rhtml
    
<% form_tag :action => 'send_invites' do -%>
<ul>
<% @friends.each do |uid,name|%>
     <li><input type="checkbox" name="friends[]" value="<%=uid%>"/><fb:profile-pic uid="<%= uid %>" size="thumb" /> <%=name%></li>
<%end%>
</ul>

You can only invite a maximum of ten people at once.
<input type="submit" value="Invite"/>
<% end -%>
    

The only difference is in the FBML version we have outputted a picture. You can style this in table or any way you want really but Facebook will only let you send 10 notifications at once and only 20 a day for each user. Now we need the send_invites action added to the controller

    
      def send_invites
        invite = render_to_string(:partial => 'invites/invite_fbml', :locals => { :inviter => @current_fb_user_id })

        friends = params[:friends]
        if friends.is_a? String
          invitees = friends
        else
          invitees = friends.values.join(",")
        end

        result_xml = fbsession.notifications_sendRequest(:to_ids => invitees, :type => 'Social Recipe', :content => invite, :invite => true, :image => "http://apps.facebook.com/socialrecipe/P1010825_tiny.jpg")

        response = CGI.unescapeHTML((result_xml/"notifications_sendrequest_response").inner_html)

        if response =~ /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix
          #need to do a confirmation redirect
          if in_facebook_canvas?
            render :text => "<fb:redirect url=\"#{response}\"/>", :layout => false
          else
            redirect_to response
          end
        else
          flash[:notice] = 'Invites sent.'
          redirect_to :controller => 'recipes', :action => 'index'
        end
      end
    

Similar to the profile box we are using a partial with render_to_string to create the FBML to send in the notification. We pass in the inviter uid to use as a local in the partial. Note that the image parameter is required in the API call even if you don’t have an image to show! Once we have a response from the API call we will either be returned a URL to redirect the user to confirm the notification or it will return nothing at all. The redirect has to be handled in different way depending on if you are in the canvas, if you are you need to output the fb:redirect tag.

If it returns nothing if either means no confirmation is necessary and the notification was sent or there was an error….. hmmmm lets be optimistic and say it was sent shall we.

    
You have been invited to upload your recipes to Social Recipe! 
<fb:name uid="<%= inviter%>" firstnameonly="false" shownetwork="false"/> wants you to add Social Recipes so you can share recipes together
<fb:req-choice url="http://www.facebook.com/add.php?api_key=916c58dd4fba835cc43389a432fa98d0" label="Add" />
<fb:req-choice url="http://apps.facebook.com/socialrecipe" label="Go to Application" />
    

The fb:req-choice will give the user in the notification the options to add the application or go to it. If you now submit the form

Wrapup

So that’s all, there are a few more things to discover in the application interface but you should have all the basic elements to create some fun apps to share with your friends and their friends. There is certainly not enough error handling in here for a production application but i’ll leave that up to the reader.

5 Responses to “Continuing Facebook Applications with Ruby On Rails”

  1. zx Says:

    Very helpful. Here are a few questions:

    - where is the key in
    <fb:req-choice /> come from? Should it be replaced by my own key? – in the render_to_string, why does the path to the partial “invite_fbml” include the controller “invite”?

    Thanks.

    -
  2. Matt Says:

    Can you provide an example for integrating a pre-existing user/pass authentication system with a Facebook authentication system?

    I have a web & mobile app that uses the normal user/pass authentication, but I’d like to extend this to allow for a Facebook app (under a specific callback directory).

  3. Adamrg07 Says:

    I have a quick question for you. I already have a website using Ruby on Rails http://www.landorslum.com. I was wondering if you knew how I could include this application on facebook without changing it’s current appearence on the website. I guess what i want to know is, can I have users on facebook have access to the application within the facebook layout, but still continue to have the exact layout that I have now on our website itself?

  4. cdb Says:

    Thanks for the articles, i’ve learned a lot reading them, and am psyched to try them out (I tried out the basic version running locally, will move on to FBML, etc, next).

    I’m also wondering how to manage having my own user system already in place on an existing app, and wanting to integrate it.

    I have a feeling the answer lies somehow in adding a “fb_id” column to my user table, and then when somebody adds the app, I compare their email address to what I have in my system, if it exists, I then store their facebook id in my ‘fb_id’ column on that record, otherwise I create a new record with as much information as I can glean from their FB profile (name, etc).

    But that’s just my idea, haven’t tried to implement it yet, if I get somewhere on it I’ll do my best to remember to post it back here.

    Cameron

  5. Hung Says:

    Wow, this is a great reference, especially since the actual documentation on rfacebook is virtually nil. Thanks a lot for writing this!

Sorry, comments are closed for this article.