# Google Data on Rails

![Google Data on Rails](https://developers.google.com/static/gdata/images/gdata_on_rails_logo.png "Google Data on Rails")   
[DocList Manager](http://doclistmanager.googlecodesamples.com) sample \[ [view demo](http://doclistmanager.googlecodesamples.com), [source](http://code.google.com/p/gdata-samples/source/browse/#svn/trunk/doclist/DocListManager) \] *Eric Bidelman, Google Data APIs Team
February 2009*

## Introduction

"Where's Ruby on the list of [client libraries](https://developers.google.com/gdata/clientlibs)?"

Motivated by the ferocious appetite of our developers and the enduring popularity
of [Ruby on Rails](http://www.rubyonrails.org/) (RoR), my colleague Jeff Fisher has forged a
[Ruby utility library](http://code.google.com/p/gdata-ruby-util/) 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](http://www.germane-software.com/~ser/software/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](http://www.ruby-lang.org/) programming language
and the Rails web-development framework. I focus on the [Documents List API](https://developers.google.com/documents) for most of
the samples, but the same concepts can be applied to any of the [Data APIs](https://developers.google.com/gdata).

## Getting Started

### Requirements

- Ruby 1.8.6 patch level 114+ [download](http://www.ruby-lang.org/en/downloads/)
- RubyGems 1.3.1+ [download](http://www.rubyonrails.org/down)
- Rails 2.2.2+ [download](http://www.rubyonrails.org/down)

### Installing the Google Data Ruby Utility Library

To obtain the library, you can either [download the library source](http://code.google.com/p/gdata-ruby-util/) 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](https://developers.google.com/gdata/auth#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](https://developers.google.com/gdata/articles/gdata_on_rails#AuthSub) or
[OAuth](https://developers.google.com/gdata/articles/gdata_on_rails#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](http://gdata-ruby-util.googlecode.com/svn/trunk/doc/classes/GData/Client.html).
If a service doesn't have a client class, use the
[`GData::Client::Base`](http://gdata-ruby-util.googlecode.com/svn/trunk/doc/classes/GData/Client/Base.html) 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)`](http://gdata-ruby-util.googlecode.com/svn/trunk/doc/classes/GData/Client/Base.html#M000021). Refer to the full [Authentication for Installed Applications](https://developers.google.com/accounts/docs/AuthForInstalledApps#Response)
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](https://developers.google.com/gdata/articles/gdata_on_rails#AuthSubAuthSubRequest) above.

#### Token management

AuthSub provides two additional handlers, [AuthSubTokenInfo](https://developers.google.com/accounts/docs/AuthSub#AuthSubTokenInfo) and
[AuthSubRevokeToken](https://developers.google.com/accounts/docs/AuthSub#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](https://developers.google.com/accounts/docs/AuthSub) 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](http://code.google.com/p/oauth-plugin/) or
Ruby [oauth gem](http://oauth.rubyforge.org/). In either case, you'll want to create a [`GData::HTTP::Request`](http://code.google.com/p/gdata-ruby-util/source/browse/trunk/lib/gdata/http/request.rb)
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`](http://code.google.com/p/gdata-samples/source/browse/trunk/doclist/DocListManager/app/controllers/doclist_controller.rb) and
[`ApplicationController`](http://code.google.com/p/gdata-samples/source/browse/trunk/doclist/DocListManager/app/controllers/application.rb) for the meat
of the DocList Manager project. You'll also want to look at
[`ContactsController`](http://code.google.com/p/gdata-samples/source/browse/trunk/doclist/DocListManager/app/controllers/contacts_controller.rb), 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](http://www.modrails.com/) 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](https://developers.google.com/gdata)
- [Google Data Ruby Utility Library](http://code.google.com/p/gdata-ruby-util/) project page
- Article: [Using Ruby with the Google Data APIs](https://developers.google.com/gdata/articles/using_ruby)
- [Download Ruby](http://www.ruby-lang.org/en/downloads/)
- [Download RubyGems and Rails](http://www.rubyonrails.org/down)

## Appendix

### Examples

The [DocList Manager](http://doclistmanager.googlecodesamples.com) is a full Ruby on Rails sample demonstrating
the topics discussed in this article. The [full source code](http://code.google.com/p/gdata-samples/source/browse/#svn/trunk/doclist/DocListManager)
is available from project hosting.

### More Examples

Retrieve a user's list of Google Documents using ClientLogin.

```
#!/usr/bin/ruby

require 'rubygems'
require 'gdata'
require 'highline/import'

def user_input(prompt='> ', echo=true)
  ask(prompt) {|q| q.echo = echo}
end

email = user_input('email: ')
pass = user_input('password: ', false)

client = GData::Client::DocList.new({:source => 'google-RailsArticleSample-v1'})
client.clientlogin(email, pass)

feed = client.get(client.authsub_scope + 'documents/private/full').to_xml

puts 'Listing all documents: ---'
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[/%3[aA](.*)$/, 1]
  puts
end
```

Mashup using the Documents List, Contacts, and Spreadsheets APIs.
This example demonstrates using secure AuthSub in RoR.

```
require 'gdata'

class YourController < ApplicationController
  before_filter :authenticate_client

  PRIVATE_KEY = '/path/to/private_key.pem'
  DOCLIST_SCOPE = 'http://docs.google.com/feeds/'
  CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'
  SPREADSHEETS_SCOPE = 'http://spreadsheets.google.com/feeds/'

  def authenticate_client
    scopes = [DOCLIST_SCOPE, SPREADSHEETS_SCOPE, CONTACTS_SCOPE]
    @client = GData::Client::Base.new({:authsub_scope => scopes.join(' '),
                                       :source => 'google-RailsArticleSample-v1'})

    if params[:token].nil? and session[:token].nil?
      next_url = url_for :controller => self.controller_name, :action => self.action_name
      secure = true
      @authsub_link = @client.authsub_url(next_url, secure, true)
      render :controller => 'your_controller', :action => 'your_action'
    elsif params[:token] and session[:token].nil?
      @client.authsub_token = params[:token]
      @client.authsub_private_key = PRIVATE_KEY
      session[:token] = @client.auth_handler.upgrade()
    end

    if session[:token]
      @client.authsub_token = session[:token]
      @client.authsub_private_key = PRIVATE_KEY  # Make sure to set your private key for subsequent requests
    end
  end

  def get_docs
    @feed = @client.get(@client.authsub_scope + 'documents/private/full').to_xml
  end
end
```

Creating, editing, and deleting a playlist in YouTube using an AuthSub token.

```
#!/usr/bin/ruby

require 'rubygems'
require 'gdata'

yt = GData::Client::YouTube.new({:source => 'google-RailsArticleSample-v1'})
yt.authsub_token = 'SESSION_TOKEN'
yt.client_id = 'CLIENT_ID'
yt.developer_key = 'DEVELOPER_KEY'

# 1.) Creating a new playlist
entry = <<-EOF
<entry xmlns="http://www.w3.org/2005/Atom"
    xmlns:yt="http://gdata.youtube.com/schemas/2007">
  <title type="text">Ruby Utility Unit Test</title>
  <summary>This is a test playlist.</summary>
</entry>
EOF

response = yt.post('http://gdata.youtube.com/feeds/api/users/default/playlists', entry).to_xml

# 2.) Updating the received playlist
edit_uri = response.elements["link[@rel='edit']"].attributes['href']
response.elements['summary'].text = 'Updated description'

response = yt.put(edit_uri, response.to_s).to_xml

# 3.) Deleting the playlist
yt.delete(edit_uri)
```

### Useful Rails Commands

If you're like me and haven't used Rails in a while, here is a list of commands worth remembering.

Update your RubyGem installation
:   `sudo gem update --system`

Update installed gems
:   `sudo gem update`

Create a new Rails with MySQL as the db
:   `rails -d mysql APP_NAME`

Start server in production on another port
:   `ruby script/server -p PORT -e production`

Clear your log files
:   `rake log:clear`