February 2009
Introduction
"Where's Ruby on the list of client libraries?"
Motivated by the ferocious appetite of our developers and the enduring popularity of Ruby on Rails (RoR), my colleague Jeff Fisher has forged a Ruby utility library from the fiery depths of Mount Doom. Mind you, it's not a full-blown client library, but it does handle the fundamentals like authentication and basic XML manipulation. It also requires you to work directly with the Atom feed using the REXML module and XPath.
Audience
This article is intended for developers interested in accessing the Google Data APIs using Ruby, specifically Ruby on Rails. It assumes the reader has some familiarity with the Ruby programming language and the Rails web-development framework. I focus on the Documents List API for most of the samples, but the same concepts can be applied to any of the Data APIs.
Getting Started
Requirements
Installing the Google Data Ruby Utility Library
To obtain the library, you can either download the library source directly from project hosting or install the gem:
sudo gem install gdata
Tip: For good measure, run gem list --local
to verify that the gem was installed properly.
Authentication
ClientLogin
ClientLogin allows your application to programmatically log in users to their Google or G Suite account. Upon validating the user's credentials, Google issues an Auth token to be referenced in subsequent API requests. The token remains valid for a set length of time, defined by whichever Google service you're working with. For security reasons and to provide your users the best experience, you should only use ClientLogin when developing installed, desktop applications. For web applications, using AuthSub or OAuth is preferred.
The Ruby library has a client class for each of the APIs. For example, use the following code snippet to log in user@gmail.com
to
the 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')
See the full list of implemented service classes.
If a service doesn't have a client class, use the
GData::Client::Base
class.
As an example, the following code forces users to log in with a G Suite account.
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)
Note: By default, the library uses HOSTED_OR_GOOGLE
for the accountType
. Possible values are HOSTED_OR_GOOGLE
,
HOSTED
, or GOOGLE
.
One of the downsides of using ClientLogin is that your application can be sent CAPTCHA challenges on failed login attempts. If that happens,
you can handle the error by calling the clientlogin()
method with its additional parameters:
client.clientlogin(username, password, captcha_token, captcha_answer)
. Refer to the full Authentication for Installed Applications
documentation for more information on dealing with CAPTCHAs.
AuthSub
Generating the AuthSubRequest URL
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)
The previous block of code creates the following URL in 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
You can also use the authsub_url
method of the client object. Each service class has set a default authsub_scope
attribute so there's no need
to specify your own.
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)
The previous block of code creates the following URL:
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
Upgrading a single-use token to a session token
AuthSub will redirect the user back to http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN
once
they have granted access to their data. Notice that the URL is just our next_url
with the single-use token
appended as a query parameter.
Next, exchange the single-use token for a long-lived session token:
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]
Secure AuthSub is very similar. The only addition is to set your private key before upgrading the token:
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]
Note: To use secure tokens, make sure to set secure=true
when requesting a
single-use token. See Generating the AuthSubRequest URL above.
Token management
AuthSub provides two additional handlers, AuthSubTokenInfo and
AuthSubRevokeToken for managing tokens. AuthSubTokenInfo
is useful for checking
the validity of a token. AuthSubRevokeToken
gives users the option of discontining access to their data. Your app should use AuthSubRevokeToken
as a best practice. Both methods are supported in the Ruby library.
To query a token's metadata:
client.auth_handler.info
To revoke a session token:
client.auth_handler.revoke
See the full AuthSub Authentication for Web Applications documentation for the full scoop on AuthSub.
OAuth
At the time of writing this article, OAuth has not been added to the GData::Auth
module.
Using OAuth in the utility library should be relatively straightforward when using the Rails oauth-plugin or
Ruby oauth gem. In either case, you'll want to create a GData::HTTP::Request
object and pass it the Authorization
header generated by each library.
Accessing feeds
GET (fetching data)
Once you've setup a client object, use its get()
method to query a Google Data feed. XPath can be used to
retrieve specific Atom elements. Here is an example of retrieving a user's Google Documents:
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 (creating new data)
Use a client's post()
method to create new data on the server. The following example will add
new_writer@example.com
as a collaborator to the document with 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 (updating data)
To update data on the server, use a client's put()
method. The following example will update a document's title.
It assumes you have a feed from a previous query.
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)
DELETE
To delete an <atom:entry> or other data from the server, use the delete()
method.
The following example will delete a document. The code assumes you have a document entry
from a previous query.
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)
Creating a new Rails application
Usually the first exercise in creating a new Rails app involves running the scaffold generators to create your MVC files.
After that, it's running rake db:migrate
to set up your database tables. However, since our application will be querying the Google Documents List
API for data, we have little need for generic scaffolding or databases. Instead, create a new application and simple controller:
rails doclist cd doclist ruby script/generate controller doclist
and make the following changes to config/environment.rb
:
config.frameworks -= [ :active_record, :active_resource, :action_mailer ] config.gem 'gdata', :lib => 'gdata'
The first line unhooks ActiveRecord
from the application.
The second line loads the gdata
gem at startup.
Lastly, I chose to connect the default route ('/
') to the documents
action in DoclistController
.
Add this line to config/routes.rb
:
map.root :controller => 'doclist', :action => 'all'
Start a controller
Since we didn't generate scaffolding, manually add an action called 'all
' to
the DoclistController
in app/controllers/doclist_controller.rb
.
class DoclistController < ApplicationController def all @foo = 'I pity the foo!' end end
and create all.html.erb
under app/views/doclist/
:
<%= @foo %>
Fire up the web server & start development
You should now be able to start the default web server by invoking ruby script/server
.
If all is well, pointing your browser to http://localhost:3000/
should display 'I pity the foo!
'.
Tip: Don't forget to remove or rename public/index.html
.
Once you have things working, take a look at my final
DoclistController
and
ApplicationController
for the meat
of the DocList Manager project. You'll also want to look at
ContactsController
, which
handles the calls to the Google Contacts API.
Conclusion
The hardest part of creating a Google Data Rails app is configuring Rails! However, a close second is deploying your application. For that, I highly recommend mod_rails for Apache. It's super easy to setup, install, and run. You'll be up and running in no time!
Resources
- List of Google Data APIs
- Google Data Ruby Utility Library project page
- Article: Using Ruby with the Google Data APIs
- Download Ruby
- Download RubyGems and Rails
Appendix
Examples
The DocList Manager is a full Ruby on Rails sample demonstrating the topics discussed in this article. The full source code is available from project hosting.