Google Data on Rail

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 feroz dos nossos desenvolvedores e pela popularidade duradoura do Ruby on Rail (RoR), meu colega Jeff Fisher criou uma biblioteca de utilitários do Ruby das profundezas do Monte Doom. Não se trata de uma biblioteca de cliente completa, mas ela processa os princípios básicos, 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 o XPath.

Público-alvo

Este artigo é destinado a desenvolvedores interessados em acessar as APIs de dados do Google usando Ruby, especificamente o Ruby on Rail. Ele pressupõe que o leitor tenha alguma familiaridade com a linguagem de programação Ruby e o framework de desenvolvimento da Web do Rail. Eu me concentro na API List List para a maioria das amostras, mas os mesmos conceitos podem ser aplicados a qualquer uma das APIs de dados.

Como começar

Requisitos

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

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

sudo gem install gdata

Dica: para uma boa avaliação, execute gem list --local para verificar se a gem foi instalada corretamente.

Autenticação

ClientLogin

O ClientLogin permite que seu aplicativo faça o login dos usuários de forma programática na conta do Google ou do G Suite. Ao validar as credenciais do usuário, o Google emite um token Auth para ser usado em solicitações de API subsequentes. O token permanece válido por um período definido, definido por qualquer serviço do Google com que você esteja trabalhando. Por motivos de segurança e para oferecer aos usuários a melhor experiência, você só deve usar ClientLogin ao desenvolver aplicativos instalados para computadores. Para aplicativos da Web, use OAuth ou OAuth.

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

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

Veja 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 a 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 desafios CAPTCHA em tentativas de login malsucedidas. Se isso acontecer, é possível processar o erro chamando o método clientlogin() com os parâmetros extras: client.clientlogin(username, password, captcha_token, captcha_answer). Consulte a documentação completa do Authentication para aplicativos instalados para mais informações sobre como lidar com CAPTCHAs.

AuthSub

Como gerar o URL tmpRequest

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 definiu um atributo authsub_scope padrão, 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 URL a seguir:

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

Como fazer upgrade de um token de uso único para um token de sessão

Depois de conceder acesso aos dados, o usuário vai ser redirecionado para http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN. O URL é somente 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 tmp seguro é muito semelhante. A única adição é definindo 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 tmpRequest acima.

Gerenciamento de tokens

Há dois gerenciadores adicionais disponíveis para o gerenciamento de tokens, tmpTokenInfo e tmpRevogarToken. AuthSubTokenInfo é útil para verificar a validade de um token. O AuthSubRevokeToken oferece aos usuários a opção de interromper o acesso aos dados. O app precisa usar AuthSubRevokeToken como prática recomendada. Ambos os métodos são aceitos na 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 do Authentication Autenticador para aplicativos da Web para saber mais sobre.

OAuth

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

O uso do OAuth na biblioteca de utilitários deve ser relativamente simples ao usar o plug-in oauth-plugin (link em inglês) ou a gem oauth do Ruby. Em ambos os casos, crie um objeto GData::HTTP::Request e transmita a ele 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() para consultar um feed de dados do Google. O XPath pode ser usado para recuperar elementos Atom específicos. Veja um exemplo de recuperação dos Documentos 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 adicionará new_writer@example.com como colaborador do 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. Você precisa ter 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 excluirá um documento. O código pressupõe que você tenha 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 Rail

Normalmente, o primeiro exercício na criação de um novo aplicativo do Beam envolve a execução de geradores de scaffold para criar os arquivos MVC. Depois disso, ele está executando rake db:migrate para configurar as tabelas do banco de dados. No entanto, como nosso aplicativo consultará a API Google Documents List para dados, precisamos de uma estrutura ou banco de dados genérico. Em vez disso, crie um novo aplicativo e um controlador simples:

rails doclist
cd doclist
ruby script/generate controller doclist

e fazer 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 gem 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 andaimes, adicione manualmente uma ação chamada "all" ao DoclistController no 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 %>

Ativar o servidor da Web e iniciar o desenvolvimento

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

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

Quando tudo estiver funcionando, dê uma olhada nos finais DoclistController e ApplicationController da carne do projeto DocList Manager. Você também pode analisar ContactsController, que processa as chamadas para a API Google Contacts.

Conclusão

A parte mais difícil da criação de um aplicativo do Google Data Rail é configurar o! No entanto, um segundo próximo é implantar o aplicativo. Para isso, é altamente recomendável usar mod_rails para o Apache. É muito fácil de configurar, instalar e executar. Tudo estará pronto em breve.

Recursos

Apêndice

Exemplos

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