Google Data on Rails

אריק בידלמן, צוות Google Data APIs
פברואר 2009

מבוא

‫"Where's Ruby on the list of client libraries?" ‏(איפה Ruby ברשימה של ספריות לקוח?)

ההתלהבות של המפתחים שלנו והפופולריות המתמשכת של Ruby on Rails (RoR) הניעו אותי ואת הקולגה שלי, ג'ף פישר, ליצור ספריית כלי עזר ל-Ruby ממעמקי הר הגורל. חשוב לציין שזו לא ספריית לקוח מלאה, אבל היא מטפלת בפעולות בסיסיות כמו אימות ומניפולציה בסיסית של XML. בנוסף, צריך לעבוד ישירות עם פיד Atom באמצעות מודול REXML ו-XPath.

קהל

המאמר הזה מיועד למפתחים שרוצים לגשת אל Google Data APIs באמצעות Ruby, ובאופן ספציפי באמצעות Ruby on Rails. המדריך מניח שלקוראים יש היכרות מסוימת עם שפת התכנות Ruby ועם מסגרת פיתוח האינטרנט Rails. ברוב הדוגמאות אני מתמקד ב-Documents List API, אבל אפשר להשתמש באותם עקרונות בכל אחד מממשקי Data API.

תחילת העבודה

דרישות

התקנה של ספריית כלי השירות של Google Data Ruby

כדי לקבל את הספרייה, אפשר להוריד את מקור הספרייה ישירות מאירוח הפרויקט או להתקין את ה-gem:

sudo gem install gdata

טיפ: כדי לוודא שה-gem הותקן בצורה תקינה, מריצים את הפקודה gem list --local.

אימות

ClientLogin

ClientLogin מאפשר לאפליקציה שלכם להתחבר לחשבון Google או G Suite של המשתמשים באופן אוטומטי. אחרי ש-Google מאמתת את פרטי הכניסה של המשתמש, היא מנפיקה אסימון אימות שאליו מתייחסות בקשות API עתידיות. האסימון תקף למשך זמן מוגדר, שנקבע על ידי שירות Google שבו אתם משתמשים. מטעמי אבטחה וכדי לספק למשתמשים את החוויה הטובה ביותר, מומלץ להשתמש ב-ClientLogin רק כשמפתחים אפליקציות מותקנות למחשב. באפליקציות אינטרנט, מומלץ להשתמש ב-AuthSub או ב-OAuth.

בספריית Ruby יש מחלקת לקוח לכל אחד מממשקי ה-API. לדוגמה, אפשר להשתמש בקטע הקוד הבא כדי להתחבר אל user@gmail.com 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')

לרשימה המלאה של סוגי השירות שהוטמעו אם לשירות אין מחלקת לקוח, צריך להשתמש במחלקה GData::Client::Base. לדוגמה, הקוד הבא מאלץ את המשתמשים להתחבר באמצעות חשבון 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)

הערה: כברירת מחדל, הספרייה משתמשת ב-HOSTED_OR_GOOGLE בשביל accountType. הערכים האפשריים הם HOSTED_OR_GOOGLE,‏ HOSTED או GOOGLE.

אחד החסרונות בשימוש ב-ClientLogin הוא שאם ניסיונות ההתחברות לאפליקציה נכשלים, יכול להיות שיוצגו בה אתגרי CAPTCHA. במקרה כזה, אפשר לטפל בשגיאה על ידי קריאה לשיטה clientlogin() עם הפרמטרים הנוספים שלה: client.clientlogin(username, password, captcha_token, captcha_answer). למידע נוסף על CAPTCHA, אפשר לעיין בתיעוד המלא בנושא אימות לאפליקציות מותקנות.

AuthSub

יצירת כתובת ה-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)

בלוק הקוד הקודם יוצר את כתובת ה-URL הבאה ב-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

אפשר גם להשתמש בשיטה authsub_url של אובייקט הלקוח. לכל סוג שירות מוגדר מאפיין ברירת מחדל authsub_scope, כך שאין צורך לציין מאפיין משלכם.

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)

בלוק הקוד הקודם יוצר את כתובת ה-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

שדרוג טוקן לשימוש חד-פעמי לטוקן סשן

מערכת AuthSub תפנה את המשתמש בחזרה אל http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN אחרי שהוא יאשר גישה לנתונים שלו. שימו לב שכתובת ה-URL היא רק next_url עם טוקן לשימוש חד-פעמי שנוסף כפרמטר שאילתה.

לאחר מכן, מחליפים את האסימון לשימוש חד-פעמי באסימון סשן לטווח ארוך:

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]

הגרסה המאובטחת של AuthSub דומה מאוד. השינוי היחיד הוא שצריך להגדיר את המפתח הפרטי לפני שמשדרגים את הטוקן:

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]

הערה: כדי להשתמש בטוקנים מאובטחים, צריך להגדיר את secure=true כשמבקשים טוקן לשימוש חד-פעמי. מידע נוסף זמין בקטע יצירת כתובת ה-URL של AuthSubRequest למעלה.

ניהול טוקנים

‫AuthSub מספק שני רכיבי handler נוספים,‏ AuthSubTokenInfo ו-AuthSubRevokeToken, לניהול טוקנים. הפרמטר AuthSubTokenInfo שימושי לבדיקת התוקף של טוקן. ‫AuthSubRevokeToken מאפשרת למשתמשים להפסיק את הגישה לנתונים שלהם. מומלץ להשתמש ב-AuthSubRevokeToken באפליקציה. שתי השיטות נתמכות בספריית Ruby.

כדי לשלוח שאילתה לגבי המטא-נתונים של טוקן:

client.auth_handler.info

כדי לבטל טוקן של סשן:

client.auth_handler.revoke

במסמכי התיעוד המלאים בנושא אימות AuthSub לאפליקציות אינטרנט אפשר לקרוא את כל הפרטים על AuthSub.

OAuth

בזמן כתיבת המאמר הזה, פרוטוקול OAuth לא נוסף למודול GData::Auth.

השימוש ב-OAuth בספריית כלי העזר אמור להיות פשוט יחסית כשמשתמשים ב-Rails oauth-plugin או ב-Ruby oauth gem. בכל מקרה, כדאי ליצור אובייקט GData::HTTP::Request ולהעביר לו את הכותרת Authorization שנוצרת על ידי כל ספרייה.

גישה לפידים

‫GET (אחזור נתונים)

אחרי שמגדירים אובייקט לקוח, משתמשים בשיטה get() שלו כדי לשלוח שאילתה לפיד נתונים של Google. אפשר להשתמש ב-XPath כדי לאחזר רכיבי Atom ספציפיים. דוגמה לאחזור מסמכי Google של משתמש:

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 (יצירת נתונים חדשים)

משתמשים בשיטה post() של לקוח כדי ליצור נתונים חדשים בשרת. בדוגמה הבאה, המשתמש new_writer@example.com יתווסף כשותף עריכה למסמך עם המזהה: 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 (עדכון נתונים)

כדי לעדכן נתונים בשרת, משתמשים בשיטה put() של הלקוח. בדוגמה הבאה מוצג עדכון של שם מסמך. ההנחה היא שיש לכם פיד משאילתה קודמת.

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)

מחיקה

כדי למחוק רכיב <atom:entry> או נתונים אחרים מהשרת, משתמשים בשיטה delete(). בדוגמה הבאה מוצג איך למחוק מסמך. הקוד מניח שיש לכם רשומה של מסמך משאילתה קודמת.

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)

יצירת אפליקציית Rails חדשה

בדרך כלל, התרגיל הראשון ביצירת אפליקציית Rails חדשה כולל הפעלה של מחוללי ה-scaffold כדי ליצור את קובצי ה-MVC. לאחר מכן, המערכת מפעילה את rake db:migrate כדי להגדיר את טבלאות מסד הנתונים. עם זאת, מכיוון שהאפליקציה שלנו תבצע שאילתות ב-API של רשימת המסמכים של Google כדי לאחזר נתונים, אין לנו צורך רב בפיגומים או במסדי נתונים גנריים. במקום זאת, יוצרים אפליקציה חדשה ובקר פשוט:

rails doclist
cd doclist
ruby script/generate controller doclist

ולבצע את השינויים הבאים ב-config/environment.rb:

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

השורה הראשונה מבטלת את הקישור של ActiveRecord לאפליקציה. השורה השנייה טוענת את ה-gem‏ gdata בהפעלה.

לבסוף, בחרתי לחבר את מסלול ברירת המחדל ('/') לפעולה documents ב-DoclistController. מוסיפים את השורה הזו אל config/routes.rb:

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

הפעלת בקר

לא יצרנו scaffolding, צריך להוסיף ידנית פעולה בשם 'all' אל DoclistController ב-app/controllers/doclist_controller.rb.

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

ולצור all.html.erb מתחת לapp/views/doclist/:

<%= @foo %>

מפעילים את שרת האינטרנט ומתחילים בפיתוח

עכשיו אמורה להיות לכם אפשרות להפעיל את שרת האינטרנט שמוגדר כברירת מחדל על ידי הפעלת הפקודה ruby script/server. אם הכול תקין, כשמפנים את הדפדפן אל http://localhost:3000/ אמור להופיע 'I pity the foo!'.

טיפ: אל תשכחו להסיר או לשנות את השם של public/index.html.

אחרי שהכל יעבוד, כדאי לעיין בDoclistController ובApplicationController הסופיים כדי להבין את המהות של פרויקט DocList Manager. כדאי גם לעיין ב-ContactsController, שמטפל בקריאות ל-Google Contacts API.

סיכום

החלק הכי קשה ביצירת אפליקציית Google Data Rails הוא הגדרת Rails. אבל מיד אחרי זה מגיע פריסת האפליקציה. לכן מומלץ מאוד להשתמש ב-mod_rails ל-Apache. ההגדרה, ההתקנה וההפעלה מאוד פשוטות. תוך זמן קצר תוכלו להתחיל להשתמש ב-Google Workspace.

משאבים

נספח

דוגמאות

DocList Manager הוא דוגמה מלאה של Ruby on Rails שממחישה את הנושאים שנדונים במאמר הזה. קוד המקור המלא זמין באירוח הפרויקט.