Google Data on Rails

Eric Bidelman,Google Data API 团队
2009 年 2 月

简介

“为什么 客户端库列表中没有 Ruby?”

在开发者对 Ruby on Rails (RoR) 的强烈需求和持久热度的推动下,我的同事 Jeff Fisher 从末日火山的炽热深处打造了一个 Ruby 实用程序库。请注意,它不是一个完整的客户端库,但确实可以处理身份验证和基本 XML 操作等基本任务。它还要求您使用 REXML 模块和 XPath 直接处理 Atom Feed。

受众群体

本文面向有兴趣使用 Ruby(尤其是 Ruby on Rails)访问 Google Data API 的开发者。 本教程假定读者对 Ruby 编程语言和 Rails Web 开发框架有一定的了解。在大多数示例中,我重点介绍 Documents List API,但相同的概念也适用于任何 Data API

使用入门

要求

  • Ruby 1.8.6 补丁级别 114 及更高版本下载
  • RubyGems 1.3.1 及更高版本 下载
  • Rails 2.2.2 及更高版本下载

安装 Google Data Ruby 实用程序库

如需获取该库,您可以直接从项目托管平台下载库源代码,也可以安装 gem:

sudo gem install gdata

提示:为确保万无一失,请运行 gem list --local 以验证该 gem 是否已正确安装。

身份验证

ClientLogin

借助 ClientLogin,您的应用可以以编程方式让用户登录其 Google 或 G Suite 账号。验证用户凭据后,Google 会签发一个身份验证令牌,供后续 API 请求引用。令牌在设定的时间长度内保持有效状态,具体时长取决于您使用的 Google 服务。出于安全原因并为了给用户提供最佳体验,您在开发已安装的桌面应用时应仅使用 ClientLogin。对于 Web 应用,建议使用 AuthSubOAuth

Ruby 库为每个 API 提供了一个客户端类。例如,使用以下代码段登录 user@gmail.com 到 Documents List Data API:

client = GData::Client::DocList.new
client.clientlogin('user@gmail.com', 'pa$$word')

The YouTube Data API would be:

client = GData::Client::YouTube.new
client.clientlogin('user@gmail.com', 'pa$$word')

请参阅已实现的服务类的完整列表。 如果服务没有客户端类,请使用 GData::Client::Base 类。例如,以下代码会强制用户使用 G Suite 账号登录。

client_login_handler = GData::Auth::ClientLogin.new('writely', :account_type => 'HOSTED')
token = client_login_handler.get_token('user@example.com', 'pa$$word', 'google-RailsArticleSample-v1')
client = GData::Client::Base.new(:auth_handler => client_login_handler)

注意:默认情况下,该库使用 HOSTED_OR_GOOGLE 作为 accountType。可能的值为 HOSTED_OR_GOOGLEHOSTEDGOOGLE

使用 ClientLogin 的缺点之一是,如果登录尝试失败,您的应用可能会收到人机识别系统挑战。如果发生这种情况,您可以调用 clientlogin() 方法并使用其附加参数 client.clientlogin(username, password, captcha_token, captcha_answer) 来处理错误。如需详细了解如何处理 CAPTCHA,请参阅完整的已安装应用的身份验证文档。

AuthSub

生成 AuthSubRequest 网址

scope = 'http://www.google.com/calendar/feeds/'
next_url = 'http://example.com/change/to/your/app'
secure = false  # set secure = true for signed AuthSub requests
sess = true
authsub_link = GData::Auth::AuthSub.get_url(next_url, scope, secure, sess)

上述代码块会在 authsub_link 中创建以下网址:

https://www.google.com/accounts/AuthSubRequest?next=http%3A%2F%2Fexample.com%2Fchange%2Fto%2Fyour%2Fapp&scope=http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F&session=1&secure=0

您还可以使用客户端对象的 authsub_url 方法。每个服务类都已设置默认的 authsub_scope 属性,因此无需指定自己的属性。

client = GData::Client::DocList.new
next_url = 'http://example.com/change/to/your/app'
secure = false  # set secure = true for signed AuthSub requests
sess = true
domain = 'example.com'  # force users to login to a G Suite hosted domain
authsub_link = client.authsub_url(next_url, secure, sess, domain)

上述代码块会创建以下网址:

https://www.google.com/accounts/AuthSubRequest?next=http%3A%2F%2Fexample.com%2Fchange%2Fto%2Fyour%2Fapp&scope=http%3A%2F%2Fdocs.google.com%2Ffeeds%2F&session=1&secure=0&hd=example.com

将一次性令牌升级为会话令牌

用户授予对其数据的访问权限后,AuthSub 会将用户重定向回 http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN。请注意,该网址只是我们的 next_url,并附加了作为查询参数的一次性令牌。

接下来,将一次性令牌换成长效会话令牌:

client.authsub_token = params[:token] # extract the single-use token from the URL query params
session[:token] = client.auth_handler.upgrade()
client.authsub_token = session[:token] if session[:token]

安全 AuthSub 非常相似。唯一新增的内容是在升级令牌之前设置私钥:

PRIVATE_KEY = '/path/to/private_key.pem'

client.authsub_token = params[:token]
client.authsub_private_key = PRIVATE_KEY
session[:token] = client.auth_handler.upgrade()
client.authsub_token = session[:token] if session[:token]

注意:如需使用安全令牌,请务必在请求一次性令牌时设置 secure=true。请参阅上文的生成 AuthSubRequest 网址

令牌管理

AuthSub 提供了两个额外的处理程序:AuthSubTokenInfoAuthSubRevokeToken,用于管理令牌。AuthSubTokenInfo 可用于检查令牌的有效性。AuthSubRevokeToken 让用户可以选择停止访问自己的数据。作为最佳实践,您的应用应使用 AuthSubRevokeToken。Ruby 库支持这两种方法。

如需查询令牌的元数据,请执行以下操作:

client.auth_handler.info

如需撤消会话令牌,请执行以下操作:

client.auth_handler.revoke

如需全面了解 AuthSub,请参阅完整的 AuthSub Web 应用身份验证文档。

OAuth

在撰写本文时,OAuth 尚未添加到 GData::Auth 模块。

使用 Rails oauth-plugin 或 Ruby oauth gem 时,在实用程序库中使用 OAuth 应该相对简单。无论哪种情况,您都需要创建一个 GData::HTTP::Request 对象,并向其传递由每个库生成的 Authorization 标头。

访问 Feed

GET(提取数据)

设置客户端对象后,使用其 get() 方法查询 Google Data Feed。XPath 可用于检索特定的 Atom 元素。以下示例展示了如何检索用户的 Google 文档:

feed = client.get('http://docs.google.com/feeds/documents/private/full').to_xml

feed.elements.each('entry') do |entry|
  puts 'title: ' + entry.elements['title'].text
  puts 'type: ' + entry.elements['category'].attribute('label').value
  puts 'updated: ' + entry.elements['updated'].text
  puts 'id: ' + entry.elements['id'].text
  
  # Extract the href value from each <atom:link>
  links = {}
  entry.elements.each('link') do |link|
    links[link.attribute('rel').value] = link.attribute('href').value
  end
  puts links.to_s
end

POST(创建新数据)

使用客户端的 post() 方法在服务器上创建新数据。以下示例会将 new_writer@example.com 添加为 ID 为 doc_id 的文档的协作者。

# Return documents the authenticated user owns
feed = client.get('http://docs.google.com/feeds/documents/private/full/-/mine').to_xml
entry = feed.elements['entry']  # first <atom:entry>

acl_entry = <<-EOF
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gAcl='http://schemas.google.com/acl/2007'>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/acl/2007#accessRule'/>
  <gAcl:role value='writer'/>
  <gAcl:scope type='user' value='new_writer@example.com'/>
</entry>
EOF

# Regex the document id out from the full <atom:id>.
# http://docs.google.com/feeds/documents/private/full/document%3Adfrk14g25fdsdwf -> document%3Adfrk14g25fdsdwf
doc_id = entry.elements['id'].text[/full\/(.*%3[aA].*)$/, 1]
response = client.post("http://docs.google.com/feeds/acl/private/full/#{doc_id}", acl_entry)

PUT(更新数据)

如需更新服务器上的数据,请使用客户端的 put() 方法。以下示例将更新文档的标题。 它假定您拥有之前查询的 Feed。

entry = feed.elements['entry'] # first <atom:entry>

# Update the document's title
entry.elements['title'].text = 'Updated title'
entry.add_namespace('http://www.w3.org/2005/Atom')
entry.add_namespace('gd','http://schemas.google.com/g/2005')

edit_uri = entry.elements["link[@rel='edit']"].attributes['href']
response = client.put(edit_uri, entry.to_s)

删除

如需从服务器中删除 <atom:entry> 或其他数据,请使用 delete() 方法。 以下示例将删除文档。该代码假设您拥有之前查询的文档条目。

entry = feed.elements['entry'] # first <atom:entry>
edit_uri = entry.elements["link[@rel='edit']"].attributes['href']
client.headers['If-Match'] = entry.attribute('etag').value  # make sure we don't nuke another client's updates
client.delete(edit_uri)

创建新的 Rails 应用

通常,创建新 Rails 应用的第一项练习涉及运行框架生成器来创建 MVC 文件。 之后,它会运行 rake db:migrate 来设置数据库表。不过,由于我们的应用将查询 Google Documents List API 以获取数据,因此我们几乎不需要通用脚手架或数据库。请改为创建新应用和简单控制器:

rails doclist
cd doclist
ruby script/generate controller doclist

并对 config/environment.rb 进行以下更改:

config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
config.gem 'gdata', :lib => 'gdata'

第一行代码将 ActiveRecord 从应用中取消挂钩。 第二行在启动时加载 gdata gem。

最后,我选择将默认路由 ('/') 连接到 DoclistController 中的 documents 操作。 将以下行添加到 config/routes.rb

map.root :controller => 'doclist', :action => 'all'

启动控制器

由于我们没有生成脚手架,因此请手动向 app/controllers/doclist_controller.rb 中的 DoclistController 添加名为“all”的操作。

class DoclistController < ApplicationController
  def all
    @foo = 'I pity the foo!'
  end
end

并在 app/views/doclist/ 下创建 all.html.erb

<%= @foo %>

启动 Web 服务器并开始开发

现在,您应该可以通过调用 ruby script/server 来启动默认 Web 服务器了。如果一切正常,将浏览器指向 http://localhost:3000/ 应该会显示“I pity the foo!”。

提示:别忘了移除或重命名 public/index.html

一切正常运行后,请查看我的最终 DoclistControllerApplicationController,了解 DocList Manager 项目的核心内容。您可能还需要查看 ContactsController,它负责处理对 Google 通讯录 API 的调用。

总结

创建 Google Data Rails 应用最难的部分是配置 Rails!不过,紧随其后的是部署应用。为此,我强烈推荐使用 Apache 的 mod_rails。设置、安装和运行都非常简单。您很快就能开始使用!

资源

附录

示例

DocList Manager 是一个完整的 Ruby on Rails 示例,演示了本文讨论的主题。您可以从项目托管服务获取完整源代码