Google Data on Rails

Eric Bidelman, équipe Google Data APIs
Février 2009

Introduction

"Où se trouve Ruby dans la liste des bibliothèques clientes ?"

Motivé par l'appétit vorace de nos développeurs et la popularité durable de Ruby on Rails (RoR), mon collègue Jeff Fisher a forgé une bibliothèque d'utilitaires Ruby dans les profondeurs ardentes du Mont Doom. Notez qu'il ne s'agit pas d'une bibliothèque cliente complète, mais qu'elle gère les principes de base tels que l'authentification et la manipulation XML de base. Vous devez également travailler directement avec le flux Atom à l'aide du module REXML et de XPath.

Audience

Cet article s'adresse aux développeurs qui souhaitent accéder aux API Google Data à l'aide de Ruby, et plus particulièrement de Ruby on Rails. Il part du principe que le lecteur connaît déjà le langage de programmation Ruby et le framework de développement Web Rails. Je me concentre sur l'API Documents List pour la plupart des exemples, mais les mêmes concepts peuvent être appliqués à n'importe quelle API Data.

Premiers pas

Conditions requises

Installer la bibliothèque d'utilitaires Ruby Google Data

Pour obtenir la bibliothèque, vous pouvez télécharger la source de la bibliothèque directement à partir de l'hébergement du projet ou installer le gem :

sudo gem install gdata

Conseil : Pour plus de sécurité, exécutez gem list --local pour vérifier que le gem a été installé correctement.

Authentification

ClientLogin

ClientLogin permet à votre application de connecter les utilisateurs à leur compte Google ou G Suite de manière programmatique. Après avoir validé les identifiants de l'utilisateur, Google émet un jeton d'authentification à référencer dans les requêtes d'API ultérieures. Le jeton reste valide pendant une durée définie par le service Google que vous utilisez. Pour des raisons de sécurité et pour offrir la meilleure expérience à vos utilisateurs, vous ne devez utiliser ClientLogin que lorsque vous développez des applications de bureau installées. Pour les applications Web, il est préférable d'utiliser AuthSub ou OAuth.

La bibliothèque Ruby comporte une classe cliente pour chacune des API. Par exemple, utilisez l'extrait de code suivant pour vous connecter à l'API Documents List Data :user@gmail.com

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')

Consultez la liste complète des classes de service implémentées. Si un service ne dispose pas de classe cliente, utilisez la classe GData::Client::Base. Par exemple, le code suivant oblige les utilisateurs à se connecter avec un compte 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)

Remarque : Par défaut, la bibliothèque utilise HOSTED_OR_GOOGLE pour accountType. Les valeurs possibles sont HOSTED_OR_GOOGLE, HOSTED ou GOOGLE.

L'un des inconvénients de l'utilisation de ClientLogin est que votre application peut recevoir des tests CAPTCHA en cas d'échec de tentatives de connexion. Dans ce cas, vous pouvez gérer l'erreur en appelant la méthode clientlogin() avec ses paramètres supplémentaires : client.clientlogin(username, password, captcha_token, captcha_answer). Pour en savoir plus sur la gestion des CAPTCHA, consultez la documentation complète sur l'authentification pour les applications installées.

AuthSub

Générer l'URL 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)

Le bloc de code précédent crée l'URL suivante dans 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

Vous pouvez également utiliser la méthode authsub_url de l'objet client. Chaque classe de service a défini un attribut authsub_scope par défaut. Vous n'avez donc pas besoin de spécifier le vôtre.

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)

Le bloc de code précédent crée l'URL suivante :

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

Mettre à niveau un jeton à usage unique en jeton de session

AuthSub redirige l'utilisateur vers http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN une fois qu'il a accordé l'accès à ses données. Notez que l'URL n'est que notre next_url avec le jeton à usage unique ajouté en tant que paramètre de requête.

Ensuite, échangez le jeton à usage unique contre un jeton de session à longue durée de vie :

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 est très semblable. La seule chose à ajouter est de définir votre clé privée avant de mettre à niveau le jeton :

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]

Remarque : Pour utiliser des jetons sécurisés, veillez à définir secure=true lorsque vous demandez un jeton à usage unique. Consultez la section Générer l'URL AuthSubRequest ci-dessus.

Gestion des jetons

AuthSub fournit deux gestionnaires supplémentaires, AuthSubTokenInfo et AuthSubRevokeToken, pour gérer les jetons. AuthSubTokenInfo est utile pour vérifier la validité d'un jeton. AuthSubRevokeToken permet aux utilisateurs de mettre fin à l'accès à leurs données. Votre application doit utiliser AuthSubRevokeToken comme bonne pratique. Les deux méthodes sont compatibles avec la bibliothèque Ruby.

Pour interroger les métadonnées d'un jeton :

client.auth_handler.info

Pour révoquer un jeton de session :

client.auth_handler.revoke

Pour en savoir plus sur AuthSub, consultez la documentation complète sur l'authentification AuthSub pour les applications Web.

OAuth

Au moment de la rédaction de cet article, OAuth n'a pas été ajouté au module GData::Auth.

L'utilisation d'OAuth dans la bibliothèque utilitaire devrait être relativement simple avec le plug-in OAuth Rails ou la gem OAuth Ruby. Dans les deux cas, vous devez créer un objet GData::HTTP::Request et lui transmettre l'en-tête Authorization généré par chaque bibliothèque.

Accéder aux flux

GET (récupération de données)

Une fois que vous avez configuré un objet client, utilisez sa méthode get() pour interroger un flux de données Google. XPath peut être utilisé pour récupérer des éléments Atom spécifiques. Voici un exemple de récupération des documents Google d'un utilisateur :

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 (création de données)

Utilisez la méthode post() d'un client pour créer des données sur le serveur. L'exemple suivant ajoute new_writer@example.com en tant que collaborateur au document dont l'ID est 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 (mise à jour des données)

Pour mettre à jour les données sur le serveur, utilisez la méthode put() d'un client. L'exemple suivant met à jour le titre d'un document. Il suppose que vous disposez d'un flux provenant d'une requête précédente.

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)

SUPPRIMER

Pour supprimer une entrée <atom:entry> ou d'autres données du serveur, utilisez la méthode delete(). L'exemple suivant supprime un document. Le code suppose que vous disposez d'une entrée de document provenant d'une requête précédente.

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)

Créer une application Rails

En général, le premier exercice de création d'une application Rails consiste à exécuter les générateurs de scaffold pour créer vos fichiers MVC. Ensuite, il exécute rake db:migrate pour configurer vos tables de base de données. Toutefois, comme notre application interrogera l'API Google Documents List pour obtenir des données, nous n'avons pas vraiment besoin d'échafaudages ni de bases de données génériques. Créez plutôt une application et un contrôleur simples :

rails doclist
cd doclist
ruby script/generate controller doclist

et apportez les modifications suivantes à config/environment.rb :

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

La première ligne détache ActiveRecord de l'application. La deuxième ligne charge le gem gdata au démarrage.

Enfin, j'ai choisi de connecter la route par défaut (/) à l'action documents dans DoclistController. Ajoutez la ligne suivante à config/routes.rb :

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

Démarrer un contrôleur

Comme nous n'avons pas généré de scaffolding, ajoutez manuellement une action appelée "all" à DoclistController dans app/controllers/doclist_controller.rb.

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

et créez all.html.erb sous app/views/doclist/ :

<%= @foo %>

Lancer le serveur Web et commencer le développement

Vous devriez maintenant pouvoir démarrer le serveur Web par défaut en appelant ruby script/server. Si tout se passe bien, l'adresse http://localhost:3000/ devrait afficher I pity the foo!.

Conseil : N'oubliez pas de supprimer ou de renommer public/index.html.

Une fois que tout fonctionne, jetez un coup d'œil à mes DoclistController et ApplicationController finaux pour le cœur du projet DocList Manager. Vous devez également examiner ContactsController, qui gère les appels à l'API Google Contacts.

Conclusion

La partie la plus difficile de la création d'une application Rails Google Data consiste à configurer Rails. Toutefois, le déploiement de votre application arrive en deuxième position. Pour cela, je vous recommande vivement mod_rails pour Apache. Il est très facile à configurer, à installer et à exécuter. Vous serez opérationnel en un rien de temps !

Ressources

Annexe

Exemples

Le gestionnaire DocList est un exemple complet de Ruby on Rails qui illustre les sujets abordés dans cet article. Le code source complet est disponible sur l'hébergement du projet.