At the Forge - Working with Facebook
Web sites have become increasingly sophisticated during the past few years, providing a wide variety of applications to the public at large. Many popular sites now offer a variety of APIs, making it possible to interact with the sites, or just retrieve data, from within a program other than an interactive Web browser.
One of the most sophisticated and popular APIs to be unveiled in recent months is from Facebook. Facebook, as you probably have heard, was started by Mark Zuckerberg when he was a student at Harvard. He has since dropped out of college and has led Facebook to be one of the largest and best-known social-networking Web sites, offering people a chance to find and connect with friends and individuals with similar interests.
Facebook has become enormously popular in the last few years, particularly among US university students. But in mid-2007, Facebook unveiled an API that was far beyond what most other sites were doing. This API did not make it particularly easy to retrieve data from Facebook or to perform searches against its extremely large database. Rather, it was designed to let individual developers create new applications that could fit into Facebook's existing site.
If the first few months are any indication, Facebook's application platform has been a wild success. According to a report published by O'Reilly Radar in October 2007, more than 4,000 applications for Facebook have been released since the platform was first unveiled. Some applications have become staggeringly popular; the report estimates that these applications get more than 30 million page views per day, which works out to more than 2% of all Facebook page views.
Other social-networking sites have realized that they must respond in kind. Both LinkedIn and MySpace are (at the time of this writing) working on APIs of their own. But, it remains to be seen if their APIs will provide the deep integration that Facebook is offering. Granted, not every Facebook application is good, and many of them are getting far fewer than the millions of users enjoyed by the top tier.
Whether Facebook turns out to prevail in the social-networking wars is an interesting topic to debate, and it is being discussed at length by business reporters and those interested in what's known as Web 2.0. What's more interesting to us, as Web/database developers, is the fact that Facebook has provided programmers with an enormous opportunity, making it possible for us to add our own applications to their site.
Last month, we created a simple “Hello, world” application that lived on our own server and was powered by Ruby on Rails. But, this application wasn't designed to be served up on its own. Rather, it is meant to be invoked via Facebook. When people go to the URL http://apps.facebook.com/rmlljatf, they will stay on Facebook, with the look and feel of the page remaining that of Facebook. But the contents of that page—currently, nothing more than “Hello from Facebook”—are generated dynamically by a Rails application sitting on my server, atf.lerner.co.il. Think of Facebook as a giant, smart proxy server, transparently passing certain HTTP requests to my server whenever someone tries my application.
This month, I explain how Facebook lets us do much more than display “Hello, world” messages. I show how we can retrieve and display information from Facebook and take an initial look at how we can use Facebook's FBML markup languages.
I also continue to develop the application I created last month—named rmlljatf—which I created using the Ruby on Rails framework in general and the RFacebook plugin for Rails in particular. See Resources for information on where to obtain this software.
Last month, we saw how we could create a very simple “Hello, world” application using Ruby on Rails and RFacebook. However, it's not that exciting to produce such output. For example, how do we know that the person is really logged in to Facebook? (Beyond the fact that the page is rendered under the apps.facebook.com hostname and has the look and feel of the Facebook page, that is.) And, where are all the nifty, cool Facebook features we have come to expect, which we would expect to use from within a Facebook application?
If this were a normal Web/database application, we simply would create an SQL query, retrieve information about the current user from the database and display it. For example, if we were interested in retrieving a list of the current user's friends, we would write something like this:
SELECT F.friend_two_id, P.first_name, P.last_name FROM Friends F, People P WHERE F.friend_one_id = 123 AND F.friend_two_id = P.id
The above, of course, assumes that we have two tables. The first table is named People, in which each person has an ID, a first name and a last name. The second table is named Friends, and it indicates who is friends with whom; each friendship is indicated with the friend_one_id and friend_two_id columns, each of which is a foreign key to People.id. Modeling friends in this way requires two rows for each friendship. This might not be the best way to keep track of links, but it reduces the complexity of the logic in SQL queries.
If we were using a straight Rails application, we could eliminate the SQL altogether, relying on the automatic way in which Rails retrieves such data. For example, we could say:
@friends = @person.friends
This automatically would fire off an SQL query not unlike the one we saw above, albeit behind the scenes. The advantage is not only that we get to write (and read and debug) less code, but also that we can think at a higher level of abstraction, looking at our users in terms of people and links, rather than rows, columns and tables.
Either of these techniques would work fine with Facebook, except for one little problem: we don't have access to the database. Rather, we have to ask Facebook for data, authenticating ourselves as a particular user within a particular application. Only after we have told Facebook who we are can we gain access to the data. Moreover, Facebook makes it easy for users to share only particular pieces of information with third-party applications (and other users), so you cannot be sure you will have access to everything.
Much of the Facebook developer documentation has to do with the ways in which you can retrieve information about current users and their friends. However, we will ignore that for now, because RFacebook pulls all of that together, as well as the authentication tokens that you need, into a single fbsession function. For example, you can write:
@friend_uids = fbsession.friends_get.uid_list
and @friend_uids will be populated with a list of the user IDs for the current user's friends. We even can display this:
@friend_uids = fbsession.friends_get.uid_list render :text => "<p>#{@friend_uids.join(', ')}</p>" return
To review, fbsession is our handle into the Facebook API. fbsession.friends_get is not merely an array of friends; rather, it is an object of type Facepricot. If this seems like an odd name to you, consider that a popular XML-parsing tool for Ruby is called Hpricot. As you can imagine, Facepricot is a Facebook-specific extension of Hpricot, which allows you to navigate through the response as if it were an Hpricot document or use Facebook-specific shortcuts. One such shortcut is seen above, as the uid_list method. Although we also could have retrieved the list of friend uids using Hpricot, this is more natural, as well as more readable and terse.
Indeed, we also could have written the above code as:
@friend_uids = fbsession.friends_get.search("//uid").map{|xmlnode| xmlnode.inner_html} render :text => @friend_uids.join(', ') return
But, unless you're doing something particularly complicated, you probably don't want to that.
Once we have retrieved the user's friends' uids, we can ask Facebook to give us some information about each one, using fbsession's users_getInfo method:
@friendsInfo = fbsession.users_getInfo(:uids => @friend_uids, :fields => ["first_name", "last_name"])
Notice that we're using instance variables (names starting with @) rather than plain-old variables. This ensures that the variables will be visible within our views. For example, we could render the above within our controller:
@friends_info = fbsession.users_getInfo(:uids => @friend_uids, :fields => ["first_name", "last_name"]) output = "" @friends_info.user_list.each do |friend| output << "<p>#{friend.first_name} #{friend.last_name}</p>\n" end render :text => output return
In the first line, we use fbsession.users_getInfo to invoke the getInfo method from the Facebook API. (Indeed, fbsession provides us with an interface to the entire Facebook API, albeit with some character translation along the way.) users_getInfo takes two parameters: a list of user IDs about which to retrieve information and then the fields we want to retrieve about them.
For example, perhaps we want to find out whether each of our friends is male or female, as well as how many messages they have on their wall. We can do this by modifying our users_getInfo query, as well as by changing our output:
@friends_info = fbsession.users_getInfo(:uids => @friend_uids, :fields => ["first_name", "last_name", "sex", "wall_count"]) output = "" @friends_info.user_list.each do |friend| output << "<p>#{friend.first_name} #{friend.last_name} (#{friend.sex}), with #{friend.wall_count} hits on their wall.</p>\n" end render :text => output return
Sure enough, this produces a list of our friends, along with their stated sex and the number of hits on their wall. Behind the scenes, our call to users_getInfo is sending a request to Facebook's servers. Facebook authenticates our request and then sends a response. Although the response is in XML, the Facepricot object provides us with some convenience functions that make it easy to work with what it provides.
The above code might work, but you would be hard-pressed to say that it was elegant. If nothing else, Rails programmers are consistent about their praise for the MVC paradigm in Web development. That is, you want to have a clear separation between the back-end data model, the controller that handles business logic and the way in which displayed items are rendered on the user's view or screen.
Luckily, it's easy to modify the way in which these things are displayed. Rather than collecting the textual output in a variable (named output in our above examples), we can define our entire method as:
def facebook @friend_uids = fbsession.friends_get.uid_list @friends_info = fbsession.users_getInfo(:uids => @friend_uids, :fields => ["first_name", "last_name", "sex", "wall_count"]) end
We then create (or modify, if you still have your view from last time) facebook.rhtml, which looks like:
<% @friends_info.user_list.each do |userInfo| %> <ul> <li><%= userInfo.first_name %> <%= userInfo.last_name %></li> </ul> <% end %>
In other words, we iterate through each element in our list of friends, pulling out their names. We can use all the information we have captured, not just the names:
<% @friends_info.user_list.each do |userInfo| %> <ul> <li><%= userInfo.first_name %> <%= userInfo.last_name %> (<%= userInfo.sex %>), wall count <%= userInfo.wall_count %></li> </ul> <% end %>
But, wait one moment—we can do even better than this! Because we are rendering things within Facebook, we can take advantage of FBML, the Facebook Markup Language. FBML is an extended subset of HTML, which is a fancy way of saying that it adds some Facebook-specific tags while removing some standard HTML tags. In any event, it allows us to create a variety of lists, interfaces and functionality that are common to Facebook applications and include them in our applications. For example, let's change our view to the following:
<% @friends_info.user_list.each do |userInfo| %> <ul> <li><fb:name uid="<%= userInfo.uid -%>" target="_blank" /><fb:profile-pic\ uid="<%= userInfo.uid -%>" linked="true" /></li> </ul> <% end %>
Now we're iterating over the same list. But, instead of rendering things directly from Ruby, we're using Ruby to pass the friend's user ID to FBML tags. Each FBML tag takes one or more arguments, passed in the form of HTML/XML attributes. In this case, we have used two FBML tags: fb:name, which displays a user's name, and fb:profile-pic, which displays the user's picture.
As you can see, we have passed each tag the uid attribute, then used some rhtml to bring in the user's ID. We also have passed the linked attribute to indicate that the picture should be a link to the user's profile. (The name is linked to the profile by default, so we don't need to say anything about that.) I have been quite impressed by the number and types of attributes that Facebook's developer API provides, going so far as to let us indicate whether we want to have the name rendered in the possessive form.
Facebook has provided application developers with a rich and interesting API that goes far beyond retrieving and storing data. It allows us to create applications that truly do sit within Facebook. Next month, we'll look at how we can have a Facebook application that stores its own data and integrates that data along with the user's Facebook profile.
Resources
Facebook developer information is at developers.facebook.com. This includes documentation, a wiki and many code examples. One article on the wiki specifically addresses Ruby development: wiki.developers.facebook.com/index.php/Using_Ruby_on_Rails_with_Facebook_Platform.
Ruby on Rails can be downloaded from rubyonrails.com. Of course, Rails is written in the Ruby language, which is almost certainly included in your distribution, and it also can be downloaded from www.ruby-lang.org.
The RFacebook gem for Ruby and the companion RFacebook plugin for Rails developers can be retrieved from rfacebook.rubyforge.org.
Hpricot, written by the prolific Ruby programmer “why the lucky stiff”, is at code.whytheluckystiff.net/hpricot. I have found it to be useful in many Ruby programs I've written, but it is especially useful in the context of RFacebook, given the central role of XML and the Facepricot extension.
Chad Fowler, a well-known Ruby developer, has developed a different Rails plugin (Facebooker) for working with Facebook. You can download the code, as well as learn more about the design principles behind his plugin, at www.chadfowler.com/2007/9/5/writing-apis-to-wrap-apis.
Finally, O'Reilly Media published a 30-page report in October 2007 describing the Facebook application platform. The report is meant for managers and marketing people, but even programmers can learn something from this (admittedly expensive) report, which describes the number of applications that have been deployed, as well as the types of things people are doing. Programmers won't learn enough from this for it to be worth buying, but it might well be worth finding and reading a copy that a more business-oriented friend has bought.
Reuven M. Lerner, a longtime Web/database developer and consultant, is a PhD candidate in learning sciences at Northwestern University, studying on-line learning communities. He recently returned (with his wife and three children) to their home in Modi'in, Israel, after four years in the Chicago area.