שימוש ב-Ruby עם ממשקי ה-API של Google Data

ג'וקאן הרטמן, צוות Google Data APIs
אפריל 2008

מבוא

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

כפי שאתה זוכר במאמר בנושא כתובות אתרים, ממשקי ה-API של נתוני Google משתמשים ב-פרוטוקול Atom Publishing כדי לייצג, ליצור ולעדכן משאבי אינטרנט. היופי של הפרוטוקול הזה הוא שבפעלים רגילים של HTTP נעשה שימוש כדי ליצור בקשות שנענות באמצעות קודי סטטוס רגילים של HTTP.

השימושים המפורטים במאמר הזה הם GET כדי לאחזר תוכן, POST להעלאת תוכן חדש ו-PUT כדי לעדכן תוכן קיים. חלק מהקודים הרגילים שעשויים להופיע כשמשתמשים בממשקי API של Google Data הם 200 כדי לייצג את ההצלחה באחזור של פיד או רשומה, או את הערך 201 כדי לייצג את היצירה או העדכון של משאב בהצלחה. אם משהו השתבש, כמו למשל כשנשלחת בקשה פגומה, יישלח קוד 400 (כלומר, 'בקשה שגויה'). בגוף התגובה תופיע הודעה מפורטת יותר, עם הסבר מדויק על הבעיה.

Ruby מספק אפשרות טובה לניפוי באגים כחלק מהמודול 'Net'. כדי לוודא שדוגמאות הקוד האלו יהיו קצרות יחסית, לא הפעלתי אותן כאן.

קבלה והתקנה של Ruby

אם משתמשים ב-Linux, אפשר להתקין את Ruby באמצעות רוב מערכות ניהול החבילות. למערכות הפעלה אחרות וכדי לקבל את קוד המקור המלא, יש להיכנס לכתובת http://www.ruby-lang.org/en/downloads/. Irb, המעטפת האינטראקטיבית שבה נשתמש בדוגמאות האלה צריכה להיות מותקנת כברירת מחדל. בהמשך לדוגמאות הקוד שרשומות כאן, צריך גם להתקין את XmlSimple – זו ספרייה קטנה כדי לנתח קובצי XML בתוך מבני הנתונים של Ruby. כדי לקבל/להתקין את XmlSimple, בקר בכתובת http://xml-Simple.rubyforge.org/

ברגע שיוצרים עותק של Ruby במחשב, אפשר להשתמש בחבילה Net:HTTP כדי לשלוח בקשות בסיסיות לשירותי הנתונים של Google. בקטע שבהמשך מוסבר איך לבצע את פעולות הייבוא הנדרשות מהמעטפת האינטראקטיבית של רובי. כדי לעשות זאת אנחנו דורשים את החבילה 'net/http', לנתח את כתובת ה-URL של פיד הווידאו בעל הדירוג הגבוה ביותר ב-YouTube, ואז לבצע בקשת HTTP GET.

irb(main):001:0> require 'net/http'
=> true
irb(main):002:0> youtube_top_rated_videos_feed_uri = \
'http://gdata.youtube.com/feeds/api/standardfeeds/top_rated'
=> "http://gdata.youtube.com/feeds/api/standardfeeds/top_rated"
irb(main):003:0> uri = \
URI.parse(youtube_top_rated_videos_feed_uri)
=> #<URI::HTTP:0xfbf826e4 URL:http://gdata.youtube.com/feeds/api/standardfeeds/top_rated>

irb(main):004:0> uri.host
=> "gdata.youtube.com"
irb(main):005:0> Net::HTTP.start(uri.host, uri.port) do |http|
irb(main):006:1* puts http.get(uri.path)
irb(main):007:1> end
#<Net::HTTPOK:0xf7ef22cc>

הבקשה הזו הייתה אמורה להחליף את ה-XML למדי לשורת הפקודה. אולי שמתם לב שכל הפריטים כלולים ברכיב <feed> והם מסווגים כרכיבי <entry>. אנחנו לא צריכים לדאוג לגבי עיצוב XML עדיין, רק רציתי להסביר איך לשלוח בקשה בסיסית ל-Google Data API באמצעות HTTP. אנחנו עומדים להחליף עכשיו את ממשקי ה-API ולהתמקד בגיליונות אלקטרוניים, כי המידע שאנחנו יכולים לשלוח ולאחזר הוא 'ידידותי יותר לשורת הפקודה'.

אימות | שימוש בממשק API של Google Sheets

אנחנו מתחילים עכשיו באחזור של פיד של רכיבי כניסה. הפעם נרצה לעבוד עם הגיליונות האלקטרוניים שלנו. כדי לעשות זאת, עלינו קודם לבצע אימות באמצעות שירות חשבונות Google.

כפי שאתם זוכרים במסמכי התיעוד בנושא אימות נתונים (GData), יש שתי דרכים לבצע אימות באמצעות שירותי Google. AuthSub מיועד לאפליקציות מבוססות אינטרנט ובקיצור, כולל תהליך החלפת אסימון. היתרון האמיתי של AuthSub הוא שהאפליקציה שלכם לא צריכה לשמור את פרטי הכניסה של המשתמשים. ClientLogin מיועד ליישומים "מותקנים". בתהליך ClientLogin, שם המשתמש והסיסמה נשלחים לשירותים של Google באמצעות https, יחד עם מחרוזת המזהה את השירות שבו אתם רוצים להשתמש. המחרוזת של wise מזוהה באמצעות שירות ה-API של Google Sheets.

נחזור אל המעטפת האינטראקטיבית שלנו, אז נאמת את Google. שימו לב שאנחנו משתמשים ב-https כדי לשלוח את בקשת האימות ופרטי הכניסה שלנו:

irb(main):008:0> require 'net/https'
=> true
irb(main):009:0> http = Net::HTTP.new('www.google.com', 443)
=> #<Net::HTTP www.google.com:443 open=false>
irb(main):010:0> http.use_ssl = true
=> true
irb(main):011:0> path = '/accounts/ClientLogin'
=> "/accounts/ClientLogin"

# Now we are passing in our actual authentication data. 
# Please visit OAuth For Installed Apps for more information 
# about the accountType parameter
irb(main):014:0> data = \
irb(main):015:0* 'accountType=HOSTED_OR_GOOGLE&Email=your email' \
irb(main):016:0* '&Passwd=your password' \
irb(main):017:0* '&service=wise'

=> accountType=HOSTED_OR_GOOGLE&Email=your email&Passwd=your password&service=wise"

# Set up a hash for the headers
irb(main):018:0> headers = \
irb(main):019:0* { 'Content-Type' => 'application/x-www-form-urlencoded'}
=> {"Content-Type"=>"application/x-www-form-urlencoded"}

# Post the request and print out the response to retrieve our authentication token
irb(main):020:0> resp, data = http.post(path, data, headers)
warning: peer certificate won't be verified in this SSL session
=> [#<Net::HTTPOK 200 OK readbody=true>, "SID=DQAAAIIAAADgV7j4F-QVQjnxdDRjpslHKC3M ... [ snipping out the rest of the authentication strings ]

# Strip out our actual token (Auth) and store it
irb(main):021:0> cl_string = data[/Auth=(.*)/, 1]
=> "DQAAAIUAAADzL... [ snip ]

# Build our headers hash and add the authorization token
irb(main):022:0> headers["Authorization"] = "GoogleLogin auth=#{cl_string}"
=> "GoogleLogin auth=DQAAAIUAAADzL... [ snip ]

אישור. עכשיו, אחרי האימות, אפשר לנו לנסות לאחזר את הגיליונות האלקטרוניים שלנו באמצעות בקשה

http://spreadsheets.google.com/feeds/spreadsheets/private/full

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

# Store the URI to the feed since we may want to use it again
irb(main):023:0> spreadsheets_uri = \
irb(main):024:0* 'http://spreadsheets.google.com/feeds/spreadsheets/private/full'

# Create a simple method to obtain a feed
irb(main):025:0> def get_feed(uri, headers=nil)
irb(main):026:1> uri = URI.parse(uri)
irb(main):027:1> Net::HTTP.start(uri.host, uri.port) do |http|
irb(main):028:2* return http.get(uri.path, headers)
irb(main):029:2> end
irb(main):030:1> end
=> nil

# Lets make a request and store the response in 'my_spreadsheets'
irb(main):031:0> my_spreadsheets = get_feed(spreadsheets_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

irb(main):032:0> my_spreadsheets
=> #<Net::HTTPOK 200 OK readbody=true>

# Examine our XML (showing only an excerpt here...)
irb(main):033:0> my_spreadsheets.body
=> "<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
<id>http://spreadsheets.google.com/feeds/spreadsheets/private/full</id><updated>2008-03-20T20:49:39.211Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/>
<title type='text'>Available Spreadsheets - test.api.jhartmann@gmail.com</title><link rel='alternate' type='text/html' href='http://docs.google.com'/>
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/spreadsheets/private/full'/><link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/spreadsheets/private/full?tfe='/>
<openSearch:totalResults>6</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><entry>
<id>http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04927555739056712307.4365563854844943790</id><updated>2008-03-19T20:44:41.055Z</updated><category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/><title type='text'>test02</title><content type='text'>test02</content><link rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.4365563854844943790/private/full'/><link rel='alternate' type='text/html' href='http://spreadsheets.google.com/ccc?key=o04927555739056712307.4365563854844943790'/><link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04927555739056712307.4365563854844943790'/><author><name>test.api.jhartmann</name><email>test.api.jhartmann@gmail.com</email></author></entry><entry> ...

שוב, אנחנו רואים הרבה XML שהדגשתי למעלה, כי לא צריך לדאוג לגבי פענוח שלו משורת הפקודה. כדי להפוך את התוכן לידידותי יותר למשתמש, אפשר לנתח אותו למבנה נתונים באמצעות XmlSimple:

# Perform imports
irb(main):034:0> require 'rubygems'
=> true
irb(main):035:0> require 'xmlsimple'
=> true
irb(main):036:0> doc = \
irb(main):037:0* XmlSimple.xml_in(my_spreadsheets.body, 'KeyAttr' => 'name')

# Import the 'pp' module for 'pretty printing'
irb(main):038:0> require 'pp'
=> true

# 'Pretty-print' our XML document
irb(main):039:0> pp doc
{"totalResults"=>["6"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#spreadsheet",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>
  [{"type"=>"text",
    "content"=>"Available Spreadsheets - Test-account"}],
 "startIndex"=>["1"],
 "id"=>["http://spreadsheets.google.com/feeds/spreadsheets/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#spreadsheet",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "title"=>[{"type"=>"text", "content"=>"blank"}],
    "author"=>
     [{"name"=>["Test-account"],
       "email"=>["my email"]}],
    "id"=>
     ["http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04927555739056712307.3387874275736238738"],
    "content"=>{"type"=>"text", "content"=>"blank"},
    "link"=>
    [ snipping out the rest of the XML ]

השגת גיליונות עבודה

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

  1. קבלת המפתח של הגיליון האלקטרוני
  2. שימוש במפתח של גיליון אלקטרוני כדי להשיג את הפיד של גיליון העבודה שלנו
  3. השגת המזהה של גיליון העבודה שבו אנחנו רוצים להשתמש
  4. יש לבקש מה-cellFeeds או מה-ListFeed כדי לגשת לתוכן בפועל של גיליון העבודה.

זה אולי נשמע כמו עבודה רבה, אבל אראה לכם שקל לעשות זאת אם נכתוב מספר שיטות פשוטות. cellFeeds ו-listFeed הם שני ייצוגים שונים עבור תוכן התא של גיליון העבודה עצמו. רשימת הפידים מייצגת שורת מידע מלאה, והיא מומלצת לפרסום נתונים חדשים. ה-cellFeeds מייצג תאים בודדים ומשמש עבור עדכוני תאים בודדים או עדכוני אצווה בתאים בודדים רבים (שניהם באמצעות PUT). למידע נוסף, ניתן לעיין במסמכי ה-API של Google Sheets.

קודם כל, אנחנו צריכים לחלץ את מפתח הגיליון האלקטרוני (מודגש בפלט ה-XML שלמעלה) כדי להשיג את הפיד של גיליון העבודה:

# Extract the spreadsheet key from our datastructure
irb(main):040:0> spreadsheet_key = \ 
irb(main):041:0* doc["entry"][0]["id"][0][/full\/(.*)/, 1]
=> "o04927555739056712307.3387874275736238738"

# Using our get_feed method, let's obtain the worksheet feed
irb(main):042:0> worksheet_feed_uri = \ 
irb(main):043:0* "http://spreadsheets.google.com/feeds/worksheets/#{spreadsheet_key}/private/full"
=> "http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.3387874275736238738/private/full"

irb(main):044:0> worksheet_response = get_feed(worksheet_feed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

# Parse the XML into a datastructure
irb(main):045:0> worksheet_data = \ 
irb(main):046:0* XmlSimple.xml_in(worksheet_response.body, 'KeyAttr' => 'name')
=> {"totalResults"=>["1"], "category"=>[{"term ... [ snip ]

# And pretty-print it
irb(main):047:0> pp worksheet_data
{"totalResults"=>["1"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#worksheet",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>[{"type"=>"text", "content"=>"blank"}],
 "author"=>
  [{"name"=>["test.api.jhartmann"],
    "email"=>["test.api.jhartmann@gmail.com"]}],
 "startIndex"=>["1"],
 "id"=>
  ["http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.3387874275736238738/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#worksheet",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "title"=>[{"type"=>"text", "content"=>"Sheet 1"}],
    "rowCount"=>["100"],
    "colCount"=>["20"],
    "id"=>
     ["http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.3387874275736238738/private/full/od6"],
    "content"=>{"type"=>"text", "content"=>"Sheet 1"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full",
       "rel"=>"http://schemas.google.com/spreadsheets/2006#listfeed",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full",
       "rel"=>"http://schemas.google.com/spreadsheets/2006#cellsfeed",
       "type"=>"application/atom+xml"},
    [ snip: cutting off the rest of the XML ]

כפי שניתן לראות כאן, עכשיו יש את הקישורים (highlighted above) שמאפשרים גישה ל-listFeed ול-cellFeed. לפני שנפרט ל-ListFeed, אסביר לך במהירות אילו נתונים קיימים בגיליון האלקטרוני לדוגמה, כדי שתדע מה אנחנו מחפשים:

הגיליון האלקטרוני שלנו פשוט מאוד ונראה כך:

languageאתר, אתר אינטרנט
Javahttp://java.com
phphttp://php.net

כך נראים הנתונים האלה בפיד list:

irb(main):048:0> listfeed_uri = \
irb(main):049:0* worksheet_data["entry"][0]["link"][0]["href"]
=> "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"

irb(main):050:0> response = get_feed(listfeed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>
irb(main):051:0> listfeed_doc = \ 
irb(main):052:0* XmlSimple.xml_in(response.body, 'KeyAttr' => 'name')
=> {"totalResults"=>["2"], "category"=>[{"term" ... [ snip ]

# Again we parse the XML and then pretty print it
irb(main):053:0> pp listfeed_doc
{"totalResults"=>["2"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#list",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>[{"type"=>"text", "content"=>"Programming language links"}],
 "author"=>
  [{"name"=>["test.api.jhartmann"],
    "email"=>["test.api.jhartmann@gmail.com"]}],
 "startIndex"=>["1"],
 "id"=>
  ["http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#list",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "language"=>["java"],
    "title"=>[{"type"=>"text", "content"=>"ruby"}],
    "website"=>["http://java.com"],
    "id"=>
     ["http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca"],
    "content"=>
     {"type"=>"text", "content"=>"website: http://java.com"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca",
       "rel"=>"self",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca/1j81anl6096",
       "rel"=>"edit",
       "type"=>"application/atom+xml"}],
    "updated"=>["2008-03-20T22:19:51.739Z"]},
   {"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#list",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "language"=>["php"],
    "title"=>[{"type"=>"text", "content"=>"php"}],
    "website"=>["http://php.net"],
    "id"=>
     ["http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr"],
    "content"=>{"type"=>"text", "content"=>"website: http://php.net"},
    [ snip ]

כמו שאפשר לראות, ב-listFeed מחזיר תוכן של גיליון העבודה. לשם כך צריך ליצור רשומה בכל שורה. ההנחה היא שהשורה הראשונה בגיליון האלקטרוני מכילה את כותרות התא, ולאחר מכן יוצרת כותרות XML באופן דינמי על סמך הנתונים באותה שורה. עיון ב-XML בפועל יעזור להסביר זאת בצורה הבאה:

<?xml version='1.0' encoding='UTF-8'?><feed [ snip namespaces ]>
<id>http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full</id>
<updated>2008-03-20T22:19:51.739Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#list'/>

<title type='text'>Programming language links</title>
[ snip: cutting out links and author information ]
<entry>
    <id>http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca</id>
    [ snip: updated and category ]
    <title type='text'>java</title>
    <content type='text'>website: http://java.com</content>
    <link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca'/>
    <link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca/1j81anl6096'/>
    <gsx:language>java</gsx:language>
    <gsx:website>http://java.com</gsx:website>
</entry>
<entry>
    <id>http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr</id>
    [ snip: updated and category ]
    <title type='text'>php</title>
    <content type='text'>website: http://php.net</content>
    <link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr'/>
    <link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr/41677fi0nc'/>
    <gsx:language>php</gsx:language>
    <gsx:website>http://php.net</gsx:website>
</entry>
</feed>

לצורך השוואה מהירה, בואו נראה איך אותו מידע מיוצג בפיד cell:

# Extract the cellfeed link
irb(main):054:0> cellfeed_uri = \
irb(main):055:0* worksheet_data["entry"][0]["link"][1]["href"]
=> "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full"
irb(main):056:0> response = \ 
irb(main):057:0* get_feed(cellfeed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

# Parse into datastructure and print
irb(main):058:0> cellfeed_doc = \ 
irb(main):059:0* XmlSimple.xml_in(response.body, 'KeyAttr' => 'name')
=> {"totalResults"=>["6"], [ snip ]

irb(main):060:0> pp cellfeed_doc
{"totalResults"=>["6"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#cell",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>[{"type"=>"text", "content"=>"Programming language links"}],
 "rowCount"=>["101"],
 "colCount"=>["20"],
 "author"=>
  [{"name"=>["test.api.jhartmann"],
    "email"=>["test.api.jhartmann@gmail.com"]}],
 "startIndex"=>["1"],
 "id"=>
  ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#cell",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "cell"=>
     [{"col"=>"1",
       "row"=>"1",
       "content"=>"language",
       "inputValue"=>"language"}],
    "title"=>[{"type"=>"text", "content"=>"A1"}],
    "id"=>
     ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R1C1"],
    "content"=>{"type"=>"text", "content"=>"language"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R1C1",
       "rel"=>"self",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R1C1/8srvbs",
       "rel"=>"edit",
       "type"=>"application/atom+xml"}],
    "updated"=>["2008-03-20T22:19:51.739Z"]},
    [ snip ]

כפי שניתן לראות כאן, מוחזרות 6 רשומות, אחת לכל תא. גזרתי את כל שאר הפלט מלבד הערך של תא A1, שמכיל את המילה 'language'. ניתן לראות גם את הקישור עריכה שמופיע למעלה. הקישור מכיל מחרוזת גרסה (8srvbs) בסוף. מחרוזת הגרסה חשובה במהלך עדכון נתוני התא, כפי שנעשה בסוף המאמר. הוא מבטיח שהעדכונים לא יוחלפו. בכל פעם ששולחים בקשת PUT לעדכון נתוני תא, יש לכלול בבקשה את מחרוזת הגרסה העדכנית ביותר של התא. מחרוזת עדכון חדשה תוחזר לאחר כל עדכון.

פרסום תוכן בפיד הרשימה

הדבר הראשון שאנחנו צריכים כדי לפרסם תוכן הוא קישור ה-POST עבור רשימת הפידים. הקישור הזה יוחזר כשתתקבל בקשה לפיד הרשימה. הוא יכלול את כתובת ה-URL http://schemas.google.com/g/2005#post כערך המאפיין rel. עליך לנתח את רכיב הקישור הזה ולחלץ את מאפיין href שלו. תחילה ניצור שיטה קטנה כדי להקל על הפרסום:

irb(main):061:0> def post(uri, data, headers)
irb(main):062:1> uri = URI.parse(uri)
irb(main):063:1> http = Net::HTTP.new(uri.host, uri.port)
irb(main):064:1> return http.post(uri.path, data, headers)
irb(main):065:1> end
=> nil
# Set up our POST url
irb(main):066:0> post_url = \ 
irb(main):067:0* "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"
=> "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"

# We must use 'application/atom+xml' as MIME type so let's change our headers 
# which were still set to 'application/x-www-form-urlencoded' when we sent our 
# ClientLogin information over https
irb(main):068:0> headers["Content-Type"] = "application/atom+xml"
=> "application/atom+xml"

# Setting up our data to post, using proper namespaces
irb(main):069:0> new_row = \ 
irb(main):070:0* '<atom:entry xmlns:atom="http://www.w3.org/2005/Atom">' << 
irb(main):071:0* '<gsx:language xmlns:gsx="http://schemas.google.com/spreadsheets/2006/extended">' <<
irb(main):072:0* 'ruby</gsx:language>' << 
irb(main):073:0* '<gsx:website xmlns:gsx="http://schemas.google.com/spreadsheets/2006/extended">' <<
irb(main):074:0* 'http://ruby-lang.org</gsx:website>' << 
irb(main):075:0* '</atom:entry>'
=> "<atom:entry xmlns:atom=\"http://www.w3.org/2005/Atom\"><gsx:language ... [ snip ] 

# Performing the post
irb(main):076:0> post_response = post(post_url, new_row, headers) 
=> #<Net::HTTPCreated 201 Created readbody=true>

סטטוס 201 מציין שהפוסט שלנו הצליח.

שימוש בתאים בפיד לעדכון תוכן

ממסמכי התיעוד ניתן לראות שפיד התאים מעדיף בקשות PUT על תוכן קיים. אבל מאחר שהמידע שאוחזר מהתא התאים קודם לכן היה רק הנתונים שכבר היו בגיליון האלקטרוני שלנו, אז איך אפשר להוסיף מידע חדש? נצטרך פשוט לשלוח בקשה לכל תא ריק שאליו אנחנו רוצים להזין נתונים. הקטע הבא מראה כיצד לאחזר את התא הריק R5C1 (שורה 5, עמודה 1) שבו ברצונך להוסיף מידע על שפת התכנות Python.

המשתנה המקורי שלנו cellfeed_uri הכיל רק את ה-URI של הפיד הסלולרי עצמו. עכשיו אנחנו רוצים לצרף את התא שאנחנו רוצים לערוך, כדי לקבל את המחרוזת של גרסת התאים כדי לערוך אותה:

# Set our query URI
irb(main):077:0> cellfeed_query = cellfeed_uri + '/R5C1'
=> "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1"

# Request the information to extract the edit link
irb(main):078:0> cellfeed_data = get_feed(cellfeed_query, headers)
=> #<Net::HTTPOK 200 OK readbody=true>
irb(main):079:0> cellfeed_data.body
=> "<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gs='http://schemas.google.com/spreadsheets/2006' xmlns:batch='http://schemas.google.com/gdata/batch'>
<id>http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1</id>
<updated>2008-03-24T21:55:36.462Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#cell'/>
<title type='text'>A5</title>
<content type='text'>
</content>
<link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1'/>
<link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1/47pc'/>
<gs:cell row='5' col='1' inputValue=''>
</gs:cell>
</entry>"

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

irb(main):080:0> def get_version_string(uri, headers=nil)
irb(main):081:1> response = get_feed(uri, headers)
irb(main):082:1> require 'rexml/document'
irb(main):083:1> xml = REXML::Document.new response.body
irb(main):084:1> edit_link = REXML::XPath.first(xml, '//[@rel="edit"]')
irb(main):085:1> edit_link_href = edit_link.attribute('href').to_s
irb(main):086:1> return edit_link_href.split(/\//)[10]
irb(main):087:1> end
=> nil

# A quick test
irb(main):088:0> puts get_version_string(cellfeed_query, headers)
47pc
=> nil

באותה הזדמנות, אנחנו עשויים גם לכתוב שיטה לביצוע בקשת ה-PUT, או יותר טוב, לתת לנו לכתוב שיטה לביצוע כל העדכונים באצווה. הפונקציה שלנו תבצע מערך של גיבובים (hash) שמכילים את המשתנים הבאים:

  • :batch_id – מזהה ייחודי לכל חלק בבקשת האצווה.
  • :cell_id – מזהה התא שיש לעדכן בפורמט R#C#, כאשר תא A1 מיוצג כ-R1C1.
  • :data – הנתונים שרוצים להוסיף.

irb(main):088:0> def batch_update(batch_data, cellfeed_uri, headers)
irb(main):089:1> batch_uri = cellfeed_uri + '/batch'
irb(main):090:1> batch_request = <<FEED
irb(main):091:1" <?xml version="1.0" encoding="utf-8"?> \
irb(main):092:1" <feed xmlns="http://www.w3.org/2005/Atom" \
irb(main):093:1" xmlns:batch="http://schemas.google.com/gdata/batch" \
irb(main):094:1" xmlns:gs="http://schemas.google.com/spreadsheets/2006" \
irb(main):095:1" xmlns:gd="http://schemas.google.com/g/2005">
irb(main):096:1" <id>#{cellfeed_uri}</id>
irb(main):097:1" FEED
irb(main):098:1> batch_data.each do |batch_request_data|
irb(main):099:2* version_string = get_version_string(cellfeed_uri + '/' + batch_request_data[:cell_id], headers)
irb(main):100:2> data = batch_request_data[:data]
irb(main):101:2> batch_id = batch_request_data[:batch_id]
irb(main):102:2> cell_id = batch_request_data[:cell_id]
irb(main):103:2> row = batch_request_data[:cell_id][1,1]
irb(main):104:2> column = batch_request_data[:cell_id][3,1]
irb(main):105:2> edit_link = cellfeed_uri + '/' + cell_id + '/' + version_string
irb(main):106:2> batch_request<< <<ENTRY
irb(main):107:2" <entry>
irb(main):108:2" <gs:cell col="#{column}" inputValue="#{data}" row="#{row}"/>
irb(main):109:2" <batch:id>#{batch_id}</batch:id>
irb(main):110:2" <batch:operation type="update" />
irb(main):111:2" <id>#{cellfeed_uri}/#{cell_id}</id>
irb(main):112:2" <link href="#{edit_link}" rel="edit" type="application/atom+xml" />
irb(main):113:2" </entry>
irb(main):114:2" ENTRY
irb(main):115:2> end
irb(main):116:1> batch_request << '</feed>'
irb(main):117:1> return post(batch_uri, batch_request, headers)
irb(main):118:1> end
=> nil

# Our sample batch data to insert information about the Python programming language into our worksheet
irb(main):119:0> batch_data = [ \
irb(main):120:0* {:batch_id => 'A', :cell_id => 'R5C1', :data => 'Python'}, \ 
irb(main):121:0* {:batch_id => 'B', :cell_id => 'R5C2', :data => 'http://python.org' } ]
=> [{:cell_id=>"R5C1", :data=>"Python", :batch_id=>"A"}=>{:cell_id=>"R5C2", :data=>"http://python.org", :batch_id=>"B"}]

# Perform the update
irb(main):122:0> response = batch_update(batch_data, cellfeed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

# Parse the response.body XML and print it
irb(main):123:0> response_xml = XmlSimple.xml_in(response.body, 'KeyAttr' => 'name')
=> [ snip ]

irb(main):124:0> pp response_xml
{"title"=>[{"type"=>"text", "content"=>"Batch Feed"}],
 "xmlns:atom"=>"http://www.w3.org/2005/Atom",
 "id"=>
  ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full"],
 "entry"=>
  [{"status"=>[{"code"=>"200", "reason"=>"Success"}],
    "category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#cell",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "cell"=>
     [{"col"=>"1", "row"=>"5", "content"=>"Python", "inputValue"=>"Python"}],
    "title"=>[{"type"=>"text", "content"=>"A5"}],
    "id"=>
     ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1",
      "A"],
    "operation"=>[{"type"=>"update"}],
    "content"=>{"type"=>"text", "content"=>"Python"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1",
       "rel"=>"self",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1/49kwzg",
       "rel"=>"edit",
       "type"=>"application/atom+xml"}],
    "updated"=>["2008-03-27T15:48:48.470Z"]},
    [ snip ]

כמו שאתם יכולים לראות, הבקשה שלנו התקבלה בהצלחה, אחרי שקיבלנו את קוד התגובה 200 OK. ניתוח XML של התגובה מאפשר לנו לראות שהוחזרה הודעה נפרדת לכל :batch_id שהגדרנו במערך response_data. למידע נוסף על עיבוד אצווה, עיינו במסמכים של עיבוד אצווה ב-GData.

סיכום

כפי שראיתם, קל מאוד להשתמש במעטפת האינטראקטיבית של Ruby עם ממשקי ה-API של נתוני Google. הצלחנו לגשת לגיליונות האלקטרוניים ולגיליונות העבודה שלנו דרך הרשימה Feed ו-cellFeeds. כמו כן, הוספנו מספר נתונים חדשים באמצעות בקשת POST ולאחר מכן כתבנו שיטות לביצוע עדכון בכמות גדולה הכולל כ-120 שורות קוד בלבד. מהנקודה הזו לא צריך להיות מסובך מדי להרכיב כמה מהשיטות הפשוטות האלה בקורסים ולבנות לעצמכם מסגרת לשימוש חוזר.

אם יש לך שאלות על השימוש בכלים האלה עם ה-Google Data API המועדף עליך, אנחנו מזמינים אותך להצטרף לקבוצות הדיון.

ניתן למצוא קובץ מחלקה עם דוגמאות הקוד המפורט למעלה בכתובת http://code.google.com/p/google-data-sample-ruby

לעיון במאמר הזה!