在 Forge - 与 Facebook 数据集成
在过去的几个月中,我们一直在研究 Facebook API,它使得将第三方应用程序集成到流行的社交网络站点成为可能。Facebook 因其庞大的用户数量以及新用户快速加入的速度而引人注目。但是,对于软件开发人员来说,它也同样引人注目,因为他们突然可以访问大量用户,并可以将自己的应用程序添加到这些用户的日常 Web 体验中。
Facebook 的性质意味着大多数开发人员正在编写的应用程序更多的是娱乐性的而非实用的。因此,很容易找到名人命名游戏、内置 Facebook 功能的扩展(例如,“超级墙”)以及各种提出问题、将人们匹配在一起等的应用程序。我预计我们最终会看到一些使用 Facebook API 创建的更严肃的应用程序,但这取决于开发者社区。我认为 Facebook 应用程序的持续增长取决于开发者从他们的工作中获利的能力,但这更多是一个商业问题,而不是一个技术问题。
无论您的应用程序做什么,如果您无法跟踪有关用户的信息,它可能会非常枯燥。这可能会让您感到奇怪——毕竟,如果您正在编写 Facebook 应用程序,Facebook 不应该为您处理存储吗?
答案是否定的。虽然 Facebook 处理用户身份验证,使您能够在 Facebook 站点内部署您的应用程序,甚至提供对当前登录用户某些数据的访问权限,但它不会代表您存储数据。这意味着您要存储的任何数据都必须保存在您自己的服务器上,在您自己的数据库中。
本月,我将解释如何在 Facebook 上创建一个简单的应用程序,该应用程序允许我们从用户的 Facebook 个人资料或从我们的本地关系数据库中无缝检索数据。关键在于用户的 Facebook ID,我们将它集成到我们自己的用户数据库中。检索有关我们用户或其任何朋友的信息将需要一些关于数据存储位置的思考。但是,您很快就会看到,混合来自不同来源的数据并不像最初听起来那么困难,并且它可以带来更有趣的应用程序。
我们的应用程序将很简单——一个 Facebook 版本的著名“Hello, world”程序,它是许多书籍和课程中的第一课。但是,我们将添加两个简单的变化:首先,我们将显示用户迄今为止访问我们应用程序的次数。(因此,在您第五次访问时,您将被提醒这是您的第五次访问。)此外,您将被告知您的每个朋友访问该站点的次数。
在一个正常的 Web/数据库应用程序中,这将非常简单。首先,我们将定义一个数据库来跟踪用户、朋友和访问次数。然后,我们将编写一些代码来跟踪登录。最后,我们将创建一个页面,显示各个页面之间连接的结果,以显示人们上次访问的时间。例如,我们可以像这样构建我们的数据库表
CREATE TABLE People ( id SERIAL NOT NULL, email_address TEXT NOT NULL, encrypted_password TEXT NOT NULL, PRIMARY KEY(id), UNIQUE(email_address) ); CREATE TABLE Visits ( person_id INTEGER NOT NULL REFERENCES People, visited_at TIMESTAMP NOT NULL DEFAULT NOW(), UNIQUE(person_id, visited_at) ); CREATE TABLE Friends ( person_id INTEGER NOT NULL REFERENCES People, friend_id INTEGER NOT NULL REFERENCES People, UNIQUE(person_id, friend_id), CHECK(person_id <> friend_id) );
我们的第一个表 People 仅包含少量列,可能比您在真实系统中需要的列要少。我们跟踪用户的主键 (id)、他们的电子邮件地址(也可用作他们的登录名)以及他们的加密密码。
我们在一个单独的表中跟踪某人对我们站点的每次访问。我们不需要这样做;在 People 表中设置一个 number_of_visits 列,然后在每次访问时递增它会更容易和更快。但是,跟踪每次访问意味着我们在未来有更大的灵活性,从收集使用统计数据到阻止人们过度使用我们的系统。
最后,我们在 Friends 表中指示友谊关系。跟踪朋友关系是一项稍微棘手的事情,因为您要假设如果 A 是 B 的朋友,那么 B 也是 A 的朋友。我们可以这样做,但我的方法是在数据库中简单地输入两行,每个方向一行。要检索 ID 为 1 的 A 的朋友,我们在 Friends 表中查找 person_id = 1 的所有 friend_id 值。
所有这些看起来都相当合理和直接,并且在任何现代 Web 框架中实现它都不难。但是,如果我们想在 Facebook 应用程序中实现相同的功能,我们必须考虑到我们刚刚定义的数据库大约一半将是不必要的。我们不需要担心 Friends 表,因为那是 Facebook 做得很好的事情。而且,我们实际上也不需要担心 People 表,因为 Facebook 处理登录和身份验证。
与此同时,我们显然不能仅单独使用 Friends 表。我们需要它指向某些东西,在某个地方,以便我们可以将访问与用户关联起来。我们该怎么做?
答案是,我们不存储用户的信息,而是存储他们的 Facebook 用户 ID。因此,我们的 People 表将如下所示
CREATE TABLE People ( id SERIAL NOT NULL, facebook_session_key TEXT NOT NULL, facebook_uid TEXT NOT NULL, PRIMARY KEY(id) );
通过在我们的数据库中存储 Facebook 信息,我们有效地将我们的 id 列连接到 Facebook 提供的内容。但是,我们将如何使用此链接呢?
答案是,如果我们使用一个为我们处理底层细节的插件,我们实际上不必这样做。在过去的几个月中,我一直在使用 RFacebook;这是 Ruby on Rails 的一个插件,它使得使用 Rails 创建 Facebook 应用程序变得相当容易。首先,我使用 Rails 自带的 generate 脚本创建我的模型
./script/generate model person facebook_session_key:string ↪facebook_uid:string
这将创建一个新模型——即一个表示数据库表的 Ruby 类——名为 person.rb。虽然此脚本不会直接创建模型,但它会创建一个迁移文件,该文件在 Ruby 中定义我们的数据库表
class CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.column :facebook_session_key, :string t.column :facebook_uid, :string end end def self.down drop_table :people end end
假设我们的数据库已全部设置好,我们可以使用内置的 rake 工具(类似于 make,但在 Ruby 中)运行迁移
rake db:migrate
输出告诉我们一些正在发生的事情
== CreatePeople: migrating ====================== -- create_table(:people) NOTICE: CREATE TABLE will create implicit sequence "people_id_seq" for serial column "people.id" NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "people_pkey" for table "people" -> 0.1939s == CreatePeople: migrated (0.1944s) =============
使用 rake 和迁移的优势在于我们可以修改我们的迁移文件,更改我们的数据库定义,并在数据库设计中向前和向后移动。迁移意味着您可以跟踪您对数据库的更改,并自动升级(或降级)数据库到最新版本,而不会丢失数据。而且,果然,如果我们查看我们的数据库,我们看到它有三列,正如我们定义的那样。
接下来,我们为我们的 visits 表创建另一个模型
./script/generate model visit person_id:integer ↪visited_at:timestamp
我们将数据库迁移到最新版本
rake db:migrate
而且,果然,我们有一个带有 person_id 列的 visits 表。不幸的是,由于 Rails 迁移是用通用语言编写的,因此没有任何内置支持来处理外键和其他引用完整性检查。因此,我们在上面定义的表将指示 person_id 必须始终指向一个值。
另请注意,默认的模型生成脚本允许空值。我们可以进入迁移文件并更改此设置,但我们现在将忽略它。
现在我们已经在 People 表中为 Facebook 信息找到了一个位置,我们需要告诉 Rails 将其放在那里。我们通过添加以下行来做到这一点
acts_as_facebook_user
在模型文件 person.rb 中。默认情况下,它几乎是空的,表明我们将使用 ActiveRecord 的默认值通过 Ruby 与我们的数据库表一起工作
class Person < ActiveRecord::Base end
完成后,我们的类将如下所示
class Person < ActiveRecord::Base acts_as_facebook_user end
在我们的控制器文件中(我偷偷地重用了我们上个月所做的内容,修改了 hello 控制器中的 facebook 方法),我修改了该方法以读取
def facebook render :text => "hi" end
因为我的应用程序名为 rmlljatf,所以我可以转到以下 URL:http://apps.facebook.com/rmlljatf/,并在页面顶部看到我的“hi”。加载此页面后,我查看我的 People 表并发现...没有任何变化。毕竟,我告诉系统创建表,但我实际上并没有对其做任何事情!为了实现这一点,我需要使用内置的 fbsession 对象,它使我可以访问 Facebook 信息。然后我可以这样说
def facebook person = Person.find_or_create_by_facebook_session(fbsession) render :text => "hi" end
而且,果然,重新加载页面会在我们的 People 表中创建一个行。
接下来,我修改我的方法以在我们的 visits 表中添加一行。我可以用以下方法做到这一点
def facebook person = Person.find_or_create_by_facebook_session(fbsession) Visit.create(:person_id => person.id, :visited_at => Time.now()).save! render :text => "hi" end
一旦我以这种方式修改了 facebook 方法,每次访问该站点确实会在 visits 表中添加另一行。
现在我们应该生成一些输出,准确指示该人访问该站点的次数。为此,我们创建一个视图 (facebook.rhtml),它可以更轻松地显示内容
<p>This is your <%= @number_of_visits.ordinalize %> visit.</p>
这个简短的视图显示了实例变量 @number_of_visits 并将其转换为序数形式,这很方便。但是,这意味着我们需要在 facebook 方法中设置 @number_of_visits。我们通过添加以下行来做到这一点
@number_of_visits = ↪Visit.count(:conditions => ["person_id = ?", person.id])
换句话说,我们获取当前用户的 ID。然后,我们将该 ID 与内置的 ActiveRecord 值一起使用,以汇总用户访问该站点的次数。
最后,是时候介绍 Facebook 的魔力了。从上个月开始,我们就知道我们可以轻松显示当前用户的 Facebook 朋友;我们使用 fbsession 获取朋友列表(以及有关这些朋友的特定信息),然后迭代它们,以我们喜欢的任何方式显示它们。
现在,我们做同样的事情,但我们也创建一个哈希 @friends_visits,其中键将是 Facebook 用户 ID (uid),值将是该人访问的次数。我们给我们的哈希一个默认值 0,以防我们尝试检索不存在的键。我们还使用一些异常处理来确保我们可以处理空结果。facebook 方法的最终版本如下所示
def facebook person = Person.find_or_create_by_facebook_session(fbsession) Visit.create(:person_id => person.id, :visited_at => Time.now()) # Count the number of visits @number_of_visits = Visit.count(:conditions => ["person_id = ?", person.id]) @friend_uids = fbsession.friends_get.uid_list # Get info about friends from Facebook @friends_info = fbsession.users_getInfo(:uids => @friend_uids, :fields => ["first_name", "last_name"]) # Keep track of friend visits to the site @friends_visits = Hash.new(0) @friends_info.user_list.each do |userInfo| begin friend = Person.find_by_facebook_uid(userInfo.uid) @friends_visits[userInfo.uid] = Visit.count(:conditions => ["person_id = ?", friend.id]) rescue next end end end
换句话说,我们通过 fbsession 获取朋友信息。然后,我们迭代每个朋友,获取其 Facebook uid。通过该 UID——我们在 People 表的 facebook_uid 列中拥有该 UID——我们可以获取该人的数据库 ID,然后使用该 ID 查找该人的访问次数。
有了这个,我们可以重写视图如下以包含朋友信息
<p>This is your <%= @number_of_visits.ordinalize %> visit.</p> <% @friends_info.user_list.each do |userInfo| %> <ul> <li><fb:name uid="<%= userInfo.uid -%>" target="_blank" /> <fb:profile-pic uid="<%= userInfo.uid -%>" linked="true" /> <%= @friends_visits[userInfo.uid] %> visit(s)</li> </ul> <% end %>
果然,当您访问该页面时,它会告诉您您访问了多少次,以及每个朋友访问了多少次。
Facebook 的 API 使我们有机会思考如何构建一个无法访问某些数据的应用程序。此应用程序没有关于用户的任何身份验证信息,并且只能获取关于他们的特定数据。但是,由于我们有一个 id 列,我们可以使用它在我们的本地服务器上存储数据,然后将该数据与来自 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 程序中它都很有用,但考虑到 XML 的中心作用和 Facepricot 扩展,它在 RFacebook 的上下文中尤其有用。
最后,著名的 Ruby 开发人员 Chad Fowler 开发了一个不同的 Rails 插件 (Facebooker) 用于与 Facebook 协同工作。您可以下载代码,并了解有关他的插件背后的设计原则的更多信息,网址为 www.chadfowler.com/2007/9/5/writing-apis-to-wrap-apis。
Reuven M. Lerner,一位长期的 Web/数据库开发人员和顾问,是西北大学学习科学博士候选人,研究在线学习社区。在芝加哥地区生活了四年之后,他最近(与他的妻子和三个孩子)返回了他们在以色列莫迪因的家。