Google 采用 Rails 数据

Eric Bidelman,Google 数据 API 团队
2009 年 2 月

简介

客户端库列表中的 Ruby 在哪里?”

鉴于开发者的强烈追求和 Ruby on Rails (RoR) 的持续盛行,我的同事 Jeff Fisher 从 Doom 山的深处精心打造了一个 Ruby 实用程序库。请注意,它不是一个成熟的客户端库,但它确实能够处理身份验证和基本 XML 操作等基础知识。此外,您还需要使用 REXML 模块和 XPath 直接处理 Atom Feed。

观众

本文面向有兴趣使用 Ruby(尤其是 Ruby on Rails)访问 Google Data API 的开发者。本文档假定读者对 Ruby 编程语言和 Rails 网页开发框架比较熟悉。我将重点介绍大多数示例的 Documents List API,但这些概念同样适用于任何 Data API

开始使用

要求

安装 Google Data Ruby 实用程序库

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

sudo gem install gdata

提示:为获得妥善衡量,请运行 gem list --local 验证 gem 是否已正确安装。

身份验证

ClientLogin

借助 StreetView,您的应用可以编程方式让用户登录其 Google 或 G Suite 帐号。验证用户的凭据后,Google 会发出一个身份验证令牌,供后续 API 请求引用。该令牌在设定的时间内有效,具体取决于您使用的任何 Google 服务。出于安全考虑,并为用户提供最佳体验,只有在开发已安装的桌面应用时,才应使用 SafeFrame。对于 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

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

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 的最新消息,请参阅完整的适用于 Web 应用的 AuthSub 身份验证文档。

OAuth

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

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

访问 Feed

GET(获取数据)

设置客户端对象后,请使用其 get() 方法查询 Google 数据 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 应用的第一个练习涉及运行 Scaffold 生成器以创建 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 %>

启动网络服务器并开始开发

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

提示:请务必移除或重命名 public/index.html

一切正常后,看一看我的最终 DoclistControllerApplicationController,了解 DocList Manager 项目的部分。此外,您还需要查看 ContactsController,用于处理对 Google Contacts API 的调用。

总结

创建 Google Data Rails 应用的最难部分是配置 Rails!然后,再过一会儿,您就可以部署应用了。为此,我强烈建议将 mod_rails 用于 Apache。此功能非常易于设置、安装和运行。您很快就可以开始投放广告了!

资源

附录

示例

DocList Manager 是一个完整的 Ruby on Rails 示例,演示了本文中讨论的主题。可通过项目托管获得完整的源代码