Dados do Google no Rails

Eric Bidelman, equipe de APIs de dados do Google
fevereiro de 2009

Introdução

"Onde está o Ruby na lista de bibliotecas de cliente?"

Motivado pelo apetite voraz dos nossos desenvolvedores e pela popularidade duradoura do Ruby on Rails (RoR), meu colega Jeff Fisher criou uma biblioteca de utilitários do Ruby nas profundezas do Monte Doom. Não é uma biblioteca de cliente completa, mas processa os fundamentos, como autenticação e manipulação básica de XML. Também é necessário trabalhar diretamente com o feed Atom usando o módulo REXML e XPath.

Público-alvo

Este artigo é destinado a desenvolvedores interessados em acessar as APIs de dados do Google usando Ruby, especificamente Ruby on Rails. Ele pressupõe que o leitor tem alguma familiaridade com a linguagem de programação Ruby e o framework de desenvolvimento da Web Rails. A maioria dos exemplos se concentra na API Documents List, mas os mesmos conceitos podem ser aplicados a qualquer uma das APIs de dados.

Primeiros passos

Requisitos

Como instalar a biblioteca de utilitários do Google Data Ruby

Para conseguir a biblioteca, faça o download da fonte da biblioteca diretamente da hospedagem do projeto ou instale a gem:

sudo gem install gdata

Dica: para garantir, execute gem list --local e verifique se a gem foi instalada corretamente.

Autenticação

ClientLogin

O ClientLogin permite que seu aplicativo faça login programaticamente nas contas do Google ou do G Suite dos usuários. Ao validar as credenciais do usuário, o Google emite um token de autenticação para ser referenciado em solicitações de API subsequentes. O token permanece válido por um período de tempo definido pelo serviço do Google que você está usando. Por motivos de segurança e para oferecer a melhor experiência aos usuários, use o ClientLogin apenas ao desenvolver aplicativos instalados para computador. Para aplicativos da Web, é preferível usar o AuthSub ou o OAuth.

A biblioteca Ruby tem uma classe de cliente para cada uma das APIs. Por exemplo, use o snippet de código a seguir para fazer login em user@gmail.com na API Data de lista de documentos:

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

Confira a lista completa de classes de serviço implementadas. Se um serviço não tiver uma classe de cliente, use a classe GData::Client::Base. Por exemplo, o código a seguir força os usuários a fazer login com uma conta do 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)

Observação: por padrão, a biblioteca usa HOSTED_OR_GOOGLE para o accountType. Os valores possíveis são HOSTED_OR_GOOGLE, HOSTED ou GOOGLE.

Uma das desvantagens de usar o ClientLogin é que seu aplicativo pode receber testes de CAPTCHA em tentativas de login com falha. Se isso acontecer, chame o método clientlogin() com os parâmetros adicionais: client.clientlogin(username, password, captcha_token, captcha_answer). Consulte a documentação completa sobre Autenticação para aplicativos instalados para mais informações sobre como lidar com CAPTCHAs.

AuthSub

Como gerar o 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)

O bloco de código anterior cria o seguinte URL em 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

Também é possível usar o método authsub_url do objeto cliente. Cada classe de serviço tem um atributo authsub_scope padrão definido, então não é necessário especificar o seu.

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)

O bloco de código anterior cria o seguinte 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

Fazer upgrade de um token de uso único para um token de sessão

O AuthSub vai redirecionar o usuário de volta para http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN depois que ele conceder acesso aos dados. O URL é apenas nosso next_url com o token de uso único anexado como um parâmetro de consulta.

Em seguida, troque o token de uso único por um token de sessão de longa duração:

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]

O AuthSub seguro é muito parecido. A única adição é definir sua chave privada antes de fazer upgrade do 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]

Observação: para usar tokens seguros, defina secure=true ao solicitar um token de uso único. Consulte Como gerar o URL AuthSubRequest acima.

Gerenciamento de tokens

O AuthSub oferece dois manipuladores extras, AuthSubTokenInfo e AuthSubRevokeToken, para gerenciar tokens. AuthSubTokenInfo é útil para verificar a validade de um token. O AuthSubRevokeToken oferece aos usuários a opção de descontinuar o acesso aos dados deles. Seu app precisa usar AuthSubRevokeToken como prática recomendada. Os dois métodos são compatíveis com a biblioteca Ruby.

Para consultar os metadados de um token:

client.auth_handler.info

Para revogar um token de sessão:

client.auth_handler.revoke

Consulte a documentação completa sobre a autenticação AuthSub para aplicativos da Web.

OAuth

No momento em que este artigo foi escrito, o OAuth ainda não havia sido adicionado ao módulo GData::Auth.

Usar o OAuth na biblioteca de utilitários é relativamente simples ao usar o oauth-plugin do Rails ou a gem oauth do Ruby. Em ambos os casos, crie um objeto GData::HTTP::Request e transmita o cabeçalho Authorization gerado por cada biblioteca.

Como acessar feeds

GET (busca de dados)

Depois de configurar um objeto cliente, use o método get() dele para consultar um feed de dados do Google. O XPath pode ser usado para recuperar elementos Atom específicos. Confira um exemplo de como recuperar os documentos do Google de um usuário:

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 (criação de novos dados)

Use o método post() de um cliente para criar novos dados no servidor. O exemplo a seguir adiciona new_writer@example.com como colaborador ao documento com o 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 (atualização de dados)

Para atualizar dados no servidor, use o método put() de um cliente. O exemplo a seguir atualiza o título de um documento. Ele pressupõe que você tem um feed de uma consulta anterior.

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)

EXCLUIR

Para excluir um <atom:entry> ou outros dados do servidor, use o método delete(). O exemplo a seguir exclui um documento. O código pressupõe que você tem uma entrada de documento de uma consulta anterior.

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)

Como criar um novo aplicativo do Rails

Normalmente, o primeiro exercício na criação de um novo app Rails envolve a execução dos geradores de scaffold para criar seus arquivos MVC. Depois disso, ele executa rake db:migrate para configurar as tabelas do banco de dados. No entanto, como nosso aplicativo vai consultar a API Google Documents List para dados, não precisamos de scaffolding ou bancos de dados genéricos. Em vez disso, crie um novo aplicativo e um controlador simples:

rails doclist
cd doclist
ruby script/generate controller doclist

e faça as seguintes mudanças em config/environment.rb:

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

A primeira linha desvincula ActiveRecord do aplicativo. A segunda linha carrega a gema gdata na inicialização.

Por fim, escolhi conectar a rota padrão ('/') à ação documents em DoclistController. Adicione esta linha a config/routes.rb:

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

Iniciar um controlador

Como não geramos scaffolding, adicione manualmente uma ação chamada "all" ao DoclistController em app/controllers/doclist_controller.rb.

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

e crie all.html.erb em app/views/doclist/:

<%= @foo %>

Iniciar o servidor da Web e o desenvolvimento

Agora você pode iniciar o servidor da Web padrão invocando ruby script/server. Se tudo estiver certo, ao apontar o navegador para http://localhost:3000/, a mensagem "I pity the foo!" vai aparecer.

Dica: não se esqueça de remover ou renomear public/index.html.

Depois que tudo estiver funcionando, confira meu DoclistController e ApplicationController finais para o conteúdo do projeto DocList Manager. Também é recomendável consultar ContactsController, que processa as chamadas para a API Google Contacts.

Conclusão

A parte mais difícil de criar um app do Google Data Rails é configurar o Rails. No entanto, a segunda opção mais importante é implantar o aplicativo. Para isso, recomendo o mod_rails para o Apache. É muito fácil de configurar, instalar e executar. Em pouco tempo, tudo estará pronto para começar.

Recursos

Apêndice

Exemplos

O DocList Manager é uma amostra completa do Ruby on Rails que demonstra os tópicos discutidos neste artigo. O código-fonte completo está disponível na hospedagem do projeto.