2. 串接第三方 API 服務
2-1 目標
上一章我們已經可以用 Ruby 程序抓到資料了,這一章我們將整合進 Rails,將抓到的資料存進資料庫,並且可以更新天氣資訊。
- 建立一個 City model,然後把 API 抓取回來的城市資料存進資料庫
- 新增 cities controller 和清單頁面,讓用戶可以瀏覽城市資料
- 用戶可以更新指定城市的氣溫資訊
2-2 初始專案,建立 City Model
在 Terminal 下輸入:
rails new api_exercise
cd api_exercise
git init
編輯 Gemfile
加上 gem 'rest-client'
,然後執行 bundle
執行 rails g model city
編輯 city 的 migration 檔案 db/migrate/201703XXXXXXXX_create_cities.rb
:
class CreateCities < ActiveRecord::Migration[5.0]
def change
create_table :cities do |t|
+ t.string :juhe_id
+ t.string :province
+ t.string :city
+ t.string :district
+ t.string :current_temp
t.timestamps
end
+ add_index :cities, :juhe_id
end
end
接著執行 rake db:migrate
建立資料庫 table。
2-3 抓取城市資料儲存下來
新增 lib/tasks/dev.rake
,放在這個目錄下的 rake 檔案是用來編寫任務腳本,讓我們在 Terminal 中可以執行它:
namespace :dev do
task :fetch_city => :environment do
puts "Fetch city data..."
response = RestClient.get "http://v.juhe.cn/weather/citys", :params => { :key => "你申請的key放這裡" }
data = JSON.parse(response.body)
data["result"].each do |c|
existing_city = City.find_by_juhe_id( c["id"] )
if existing_city.nil?
City.create!( :juhe_id => c["id"], :province => c["province"],
:city => c["city"], :district => c["district"] )
end
end
puts "Total: #{City.count} cities"
end
end
執行 bundle exec rake dev:fetch_city
就會執行這個任務,把 2574 筆城市存進資料庫。
juhe_id
這個欄位的目的是存下第三方那邊的 id,這樣我們之後在更新數據的時候,就可以進行比對、避免重復新增。
2-4 在畫面上顯示出來
編輯 config/routes.rb
新增一行
Rails.application.routes.draw do
+ resources :cities
end
執行 rails g controller cities
編輯 app/controllers/cities_controller.rb
class CitiesController < ApplicationController
+ def index
+ @cities = City.all
+ end
end
新增 app/views/cities/index.html.erb
<table class="table">
<tr>
<th>Juhe ID</th>
<th>Province</th>
<th>City</th>
<th>District</th>
<th>Temp</th>
</tr>
<% @cities.each do |city| %>
<tr>
<td><%= city.juhe_id %></td>
<td><%= city.province %></td>
<td><%= city.city %></td>
<td><%= city.district %></td>
<td></td>
</tr>
<% end %>
</table>
啟動伺服器 rails s
,打開瀏覽器 http://localhost:3000/cities
就會看到城市資料了。
這裡省略了安裝 Bootstrap 的步驟,如果沒安裝也沒關系,畫面會有差異而已。
2-5 更新城市天氣
我們希望存下來當前溫度。根據 文檔 的說明,可以找到氣溫的 API 說明。
首先修改 config/routes.rb
,新增一個操作:
- resources :cities
+ resources :cities do
+ member do
+ post :update_temp
+ end
+ end
在畫面上放一個按鈕,編輯 app/views/cities/index.html
- <td></td>
+ <td>
+ <%= city.current_temp %>
+ <%= link_to "更新溫度", update_temp_city_path(city), :method => :post %>
+ </td>
新增一個 action,編輯 app/controller/cities_controller.rb
def update_temp
city = City.find(params[:id])
response = RestClient.get "http://v.juhe.cn/weather/index",
:params => { :cityname => city.juhe_id, :key => "你申請的key放這裡" }
data = JSON.parse(response.body)
city.update( :current_temp => data["result"]["sk"]["temp"] )
redirect_to cities_path
end
這樣點擊「更新溫度」後,就會更新氣溫了。
2-6 保護 API Key
在串接第三方應用時,第三方的 API Key 我們不希望寫死在程式碼裡面,一來是因為我們不想把這些敏感的 keys 放到版本控制系統裡面。二來是因為將來佈署的時候,在 production 環境下,api key 會另外申請一個不一樣,因此我們希望容易抽換。
新增 config/juhe.yml
作為設定檔,內容如下:
development:
api_key: "你申請的key放這裡"
production:
api_key: "之後佈署上production的話,key放這裡"
編輯 config/application.rb
,在最下麵插入一行:
# (略)
JUHE_CONFIG = Rails.application.config_for(:juhe)
編輯 app/controller/cities_controller.rb
和 lib/tasks/dev.rake
,把 "你申請的key放這裡"
置換成 JUHE_CONFIG["api_key"]
即可。
要注意:
- YAML 格式使用空白縮排來表達資料的階層關系,請務必縮排整齊
- YAML 格式會區分數字和字串,例如
01234
會看成1234
,如果要確保被解析成字串,請加上引號,例如"01234"
- 讀出來的 Hash 是用字串 key,不是 symbol key。是
JUHE_CONFIG["api_key"]
而不是JUHE_CONFIG[:api_key]
接著我們要告訴 Git 不要 commit 這個檔案,這樣就不用擔心 git push 會把 api key 洩漏出去。
編輯 .gitignore
,插入一行
config/juhe.yml
依照慣例,你可以複製一個 juhe.yml.example
檔案放進版本控制系統裡面,這可以給你同事當作範例參考,內容例如:
development:
api_key: "<juhe api key>"
補充參考
可以從以下的 API 服務商中找你有興趣的資料集:
- https://www.juhe.cn
- http://apistore.baidu.com
- https://www.haoduoshuju.com
- http://www.pm25.in/api_doc
- https://github.com/toddmotto/public-apis
- http://opendatachina.com/en/projects/