锻造坊 - 与 Facebook 合作
在过去的几年里,网站变得越来越复杂,为公众提供了各种各样的应用程序。许多流行的网站现在提供了各种 API,使得从交互式 Web 浏览器以外的程序中与网站交互,或者仅仅是检索数据成为可能。
最近几个月发布的最复杂和最流行的 API 之一来自 Facebook。正如您可能听说过的,Facebook 是由马克·扎克伯格在哈佛大学读书时创立的。此后,他从大学辍学,并带领 Facebook 成为最大和最著名的社交网络网站之一,为人们提供寻找和联系朋友以及具有相似兴趣的个人的机会。
Facebook 在过去几年里变得非常流行,尤其是在美国大学生中。但在 2007 年年中,Facebook 发布了一个 API,远远超出了大多数其他网站所做的。这个 API 并没有特别容易地从 Facebook 检索数据或对其极其庞大的数据库执行搜索。相反,它的设计目的是让个人开发者创建可以融入 Facebook 现有站点的新应用程序。
如果最初几个月的情况有任何指示意义,Facebook 的应用程序平台已经取得了巨大的成功。根据 O'Reilly Radar 在 2007 年 10 月发布的一份报告,自该平台首次发布以来,已经发布了超过 4,000 个 Facebook 应用程序。一些应用程序变得非常受欢迎;该报告估计,这些应用程序每天获得超过 3000 万的页面浏览量,占所有 Facebook 页面浏览量的 2% 以上。
其他社交网络站点已经意识到他们必须做出类似的回应。LinkedIn 和 MySpace 都在(在撰写本文时)开发自己的 API。但是,它们的 API 是否会提供 Facebook 正在提供的深度集成还有待观察。诚然,并非每个 Facebook 应用程序都是好的,而且其中许多应用程序的用户远远少于顶级应用程序所享有的数百万用户。
Facebook 是否会在社交网络战争中胜出是一个有趣的辩论话题,商业记者和那些对所谓的 Web 2.0 感兴趣的人正在对此进行深入讨论。对我们这些 Web/数据库开发者来说更有趣的是,Facebook 为程序员提供了一个巨大的机会,使我们有可能将我们自己的应用程序添加到他们的网站中。
上个月,我们创建了一个简单的“Hello, world”应用程序,该应用程序位于我们自己的服务器上,并由 Ruby on Rails 提供支持。但是,这个应用程序并非旨在独立提供服务。相反,它旨在通过 Facebook 调用。当人们访问 URL http://apps.facebook.com/rmlljatf 时,他们将停留在 Facebook 上,页面的外观和感觉仍然是 Facebook 的。但是该页面的内容——目前,仅仅是“Hello from Facebook”——是由位于我的服务器 atf.lerner.co.il 上的 Rails 应用程序动态生成的。可以将 Facebook 视为一个巨大的、智能的代理服务器,每当有人尝试我的应用程序时,它都会透明地将某些 HTTP 请求传递到我的服务器。
本月,我将解释 Facebook 如何让我们做更多的事情,而不仅仅是显示“Hello, world”消息。我将展示我们如何从 Facebook 检索和显示信息,并初步了解我们如何使用 Facebook 的 FBML 标记语言。
我还将继续开发我上个月创建的应用程序——名为 rmlljatf——我通常使用 Ruby on Rails 框架创建该应用程序,特别是 RFacebook 插件 for Rails。有关如何获取此软件的信息,请参阅“资源”。
上个月,我们看到了如何使用 Ruby on Rails 和 RFacebook 创建一个非常简单的“Hello, world”应用程序。然而,生成这样的输出并不令人兴奋。例如,我们如何知道这个人真的登录了 Facebook?(除了页面是在 apps.facebook.com 主机名下呈现的,并且具有 Facebook 页面的外观和感觉之外。)而且,我们一直期待的,并且期望从 Facebook 应用程序中使用的所有漂亮、酷炫的 Facebook 功能在哪里?
如果这是一个普通的 Web/数据库应用程序,我们只需创建一个 SQL 查询,从数据库中检索有关当前用户的信息并显示它。例如,如果我们有兴趣检索当前用户的朋友列表,我们将编写类似这样的内容
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
当然,以上假设我们有两个表。第一个表名为 People,其中每个人都有一个 ID、一个名字和一个姓氏。第二个表名为 Friends,它指示谁与谁是朋友;每个友谊都用 friend_one_id 和 friend_two_id 列表示,每一列都是 People.id 的外键。以这种方式对朋友建模需要为每个友谊添加两行。这可能不是跟踪链接的最佳方式,但它降低了 SQL 查询中逻辑的复杂性。
如果我们使用直接的 Rails 应用程序,我们可以完全消除 SQL,依靠 Rails 自动检索此类数据的方式。例如,我们可以说
@friends = @person.friends
这将自动触发一个 SQL 查询,与我们上面看到的查询非常相似,尽管是在幕后。优点不仅是我们编写(和阅读和调试)的代码更少,而且我们可以在更高的抽象级别上思考,从人与链接的角度来看待我们的用户,而不是行、列和表。
这些技术中的任何一种都可以在 Facebook 上正常工作,除了一个小问题:我们无法访问数据库。相反,我们必须向 Facebook 请求数据,将自己验证为特定应用程序中的特定用户。只有在我们告诉 Facebook 我们是谁之后,我们才能访问数据。此外,Facebook 让用户可以轻松地仅与第三方应用程序(和其他用户)共享特定的信息片段,因此您不能确定您将可以访问所有内容。
大部分 Facebook 开发者文档都与您可以检索有关当前用户及其朋友信息的方式有关。但是,我们现在将忽略这一点,因为 RFacebook 将所有这些以及您需要的身份验证令牌都集中到一个 fbsession 函数中。例如,您可以编写
@friend_uids = fbsession.friends_get.uid_list
并且 @friend_uids 将填充当前用户朋友的用户 ID 列表。我们甚至可以显示这个
@friend_uids = fbsession.friends_get.uid_list render :text => "<p>#{@friend_uids.join(', ')}</p>" return
回顾一下,fbsession 是我们进入 Facebook API 的句柄。fbsession.friends_get 不仅仅是一个朋友数组;相反,它是一个 Facepricot 类型的对象。如果您觉得这个名字很奇怪,请考虑一个流行的 Ruby XML 解析工具称为 Hpricot。您可以想象,Facepricot 是 Hpricot 的 Facebook 特定扩展,它允许您像浏览 Hpricot 文档一样浏览响应,或使用 Facebook 特定快捷方式。一个这样的快捷方式如上所示,即 uid_list 方法。尽管我们也可以使用 Hpricot 检索朋友 uid 列表,但这更自然,也更具可读性和简洁性。
实际上,我们也可以将上面的代码编写为
@friend_uids = fbsession.friends_get.search("//uid").map{|xmlnode| xmlnode.inner_html} render :text => @friend_uids.join(', ') return
但是,除非您正在做一些特别复杂的事情,否则您可能不想这样做。
一旦我们检索到用户朋友的 uid,我们可以要求 Facebook 使用 fbsession 的 users_getInfo 方法向我们提供有关每个朋友的一些信息
@friendsInfo = fbsession.users_getInfo(:uids => @friend_uids, :fields => ["first_name", "last_name"])
请注意,我们正在使用实例变量(名称以 @ 开头)而不是普通的变量。这确保了变量在我们的视图中是可见的。例如,我们可以在我们的控制器中呈现上述内容
@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
在第一行中,我们使用 fbsession.users_getInfo 从 Facebook API 调用 getInfo 方法。(实际上,fbsession 为我们提供了整个 Facebook API 的接口,尽管沿途进行了一些字符转换。)users_getInfo 接受两个参数:要检索信息的用户 ID 列表,以及我们要检索的关于他们的字段。
例如,也许我们想知道我们的每个朋友是男性还是女性,以及他们的墙上有多少条消息。我们可以通过修改我们的 users_getInfo 查询以及更改我们的输出来做到这一点
@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
果然,这会生成我们的朋友列表,以及他们声明的性别和他们墙上的点击次数。在幕后,我们对 users_getInfo 的调用正在向 Facebook 的服务器发送请求。Facebook 验证我们的请求,然后发送响应。尽管响应是 XML 格式,但 Facepricot 对象为我们提供了一些方便的函数,使我们可以轻松地处理它提供的内容。
上面的代码可能有效,但您很难说它很优雅。至少,Rails 程序员对 Web 开发中 MVC 范例的赞扬是一致的。也就是说,您希望在后端数据模型、处理业务逻辑的控制器以及在用户视图或屏幕上呈现显示项目的方式之间有一个清晰的分离。
幸运的是,很容易修改这些事物的显示方式。我们可以将我们的整个方法定义为
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
然后,我们创建(或修改,如果您仍然有上次的视图)facebook.rhtml,它看起来像
<% @friends_info.user_list.each do |userInfo| %> <ul> <li><%= userInfo.first_name %> <%= userInfo.last_name %></li> </ul> <% end %>
换句话说,我们遍历朋友列表中的每个元素,提取他们的名字。我们可以使用我们捕获的所有信息,而不仅仅是名字
<% @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 %>
但是,稍等一下——我们可以做得更好!因为我们是在 Facebook 中呈现事物,所以我们可以利用 FBML,即 Facebook 标记语言。FBML 是 HTML 的扩展子集,这是一种花哨的说法,即它添加了一些 Facebook 特定的标签,同时删除了一些标准的 HTML 标签。无论如何,它允许我们创建各种列表、界面和 Facebook 应用程序通用的功能,并将它们包含在我们的应用程序中。例如,让我们将我们的视图更改为以下内容
<% @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 %>
现在我们正在迭代相同的列表。但是,我们不是直接从 Ruby 呈现事物,而是使用 Ruby 将朋友的用户 ID 传递给 FBML 标签。每个 FBML 标签都接受一个或多个参数,以 HTML/XML 属性的形式传递。在本例中,我们使用了两个 FBML 标签:fb:name,它显示用户的姓名,以及 fb:profile-pic,它显示用户的图片。
正如您所看到的,我们已将每个标签传递了 uid 属性,然后使用了一些 rhtml 来引入用户的 ID。我们还传递了 linked 属性,以指示图片应该是指向用户个人资料的链接。(默认情况下,名称链接到个人资料,因此我们无需对此进行任何说明。)Facebook 的开发者 API 提供的属性的数量和类型给我留下了深刻的印象,甚至允许我们指示是否希望以所有格形式呈现名称。
Facebook 为应用程序开发者提供了一个丰富而有趣的 API,远远超出了检索和存储数据的范围。它允许我们创建真正位于 Facebook 内的应用程序。下个月,我们将了解如何拥有一个 Facebook 应用程序,该应用程序存储自己的数据并将该数据与用户的 Facebook 个人资料集成在一起。
资源
Facebook 开发者信息位于 developers.facebook.com。这包括文档、维基和许多代码示例。维基上的一篇文章专门介绍了 Ruby 开发:wiki.developers.facebook.com/index.php/Using_Ruby_on_Rails_with_Facebook_Platform。
Ruby on Rails 可以从 rubyonrails.com 下载。当然,Rails 是用 Ruby 语言编写的,几乎可以肯定包含在您的发行版中,也可以从 www.ruby-lang.org 下载。
用于 Ruby 的 RFacebook gem 和用于 Rails 开发者的配套 RFacebook 插件可以从 rfacebook.rubyforge.org 检索。
由多产的 Ruby 程序员 “why the lucky stiff” 编写的 Hpricot 位于 code.whytheluckystiff.net/hpricot。我发现它在我编写的许多 Ruby 程序中都很有用,但在 RFacebook 的上下文中尤其有用,考虑到 XML 和 Facepricot 扩展的中心作用。
著名的 Ruby 开发者 Chad Fowler 开发了一个不同的 Rails 插件 (Facebooker) 用于与 Facebook 合作。您可以下载代码,并了解有关他的插件背后的设计原则的更多信息,网址为 www.chadfowler.com/2007/9/5/writing-apis-to-wrap-apis。
最后,O'Reilly Media 在 2007 年 10 月发布了一份 30 页的报告,描述了 Facebook 应用程序平台。该报告是为管理人员和营销人员准备的,但即使是程序员也可以从这份(公认昂贵的)报告中学到一些东西,该报告描述了已部署的应用程序数量,以及人们正在做的事情类型。程序员无法从中学习到足够的东西来使其值得购买,但很可能值得找到并阅读一份更注重商业的朋友购买的副本。
Reuven M. Lerner,一位长期的 Web/数据库开发者和顾问,是西北大学学习科学专业的博士候选人,研究在线学习社区。在芝加哥地区生活了四年之后,他最近(与他的妻子和三个孩子)返回了他们在以色列莫迪因的家。