#概要
アプリを作成していて、APIで取得したjsonデータを自動で保存する必要が出てきた。
具体的には通貨レートを一日一回取得し、それを自動保存する機能である。
現状の実装では、アクション毎にAPIを叩いているが、APIの利用制限の問題から、レート情報をデータベースに保存し、それを参照する方法に変えたい。
しかし、通貨の数が多く手作業で登録するのは効率が悪い。
そこで、利用するのがseed.rbである。
#seed.rbの使い方
今回はcurrencylayerで取得したjsonデータを例に使う。
seed.rbはrailsにデフォルトで存在するファイルであり、場所はdb/seed.rb
である。
このファイルを開くと...
# Examples:
#
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first)
スターウォーズ好きが作ったことが分かる!
以下から例を使用して手順を解説する。
1. APIでjsonデータを取得
今回使うcurrencylayerは、
https://apilayer.net/api/live?access_key=アクセスコード¤cies=EUR,GBP,CAD,PLN&source=USD&format=1
というように、基になる通貨と変換先の通貨を指定してアクセスすれば、以下のようなjsonデータが返ってくる。(数字はちょこちょこ変えてます。)
{
"success":true,
"terms":"https:\/\/currencylayer.com\/terms",
"privacy":"https:\/\/currencylayer.com\/privacy",
"timestamp":8765456787654,
"source":"USD",
"quotes":{
"USDEUR":0.81567,
"USDGBP":0.5435435,
"USDCAD":1.4232353,
"USDPLN":3.4987654
}
}
2. APIをrailsで叩く
今回はNET::HTTPを利用してAPIを取得して、
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse('https://apilayer.net/api/live?access_key=アクセスコード¤cies=EUR,GBP,CAD,PLN&source=USD&format=1')
json = Net::HTTP.get(uri) #NET::HTTPを利用してAPIを叩く
result = JSON.parse(json) #返ってきたjsonデータをrubyの配列に変換
puts result
すると以下のように結果が配列で出力される
{"seccess"=>"true", "terms"=>"https:\/\/currencylayer.com\/terms",..., "source"=>"USD",
"quotes"=>[{"USDEUR"=>"0.81567", "USDGBP"=>"0.5435435", "USDCAD"=>"1.4232353","USDPLN"=>"3.4987654"}]}
3. 結果をseed.rbで自動登録
今回のモデルの設計としては、テーブル名はexchange、カラムはcurrencyとrateの2つ。
表にすると以下のような感じ。
currency | rate |
---|---|
USDEUR | 0.81567 |
USDGBR | 0.54354 |
USDCAD | 1.423235 |
以下のように書けば自動登録できる。
result['quotes'].each do |key, value|
Exchange.create(currency: key, rate: value)
end
毎日一回レートを更新(書き替える)場合は
result['quotes'].each do |key, value|
rate = Exchange.find_by(currency: key)
rate.update(rate: value)
end
こんな感じになる。
Memo: キーと値を両方取得して保存する方法に苦戦した。キーを基に値を取得や、そのその逆は簡単であるが、両方を取得する方法を紹介する記事は少なかった。
その中で、この記事[Ruby] 便利な組み込みクラスのメソッド達(Hash編)が非常に参考になった。
#まとめ
コードのサンプルは以下の通りである。
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse('https://apilayer.net/api/live?access_key=アクセスコード¤cies=EUR,GBP,CAD,PLN&source=USD&format=1')
json = Net::HTTP.get(uri) #NET::HTTPを利用してAPIを叩く
result = JSON.parse(json) #返ってきたjsonデータをrubyの配列に変換
result['quotes'].each do |key, value|
rate = Exchange.find_by(currency: key)
rate.update(rate: value)
end
このコードの注意点として、データを書き換えるためにfind_byとupdateを利用しているので最初に何かしらのデータを登録しておく必要がある。(ifでデータがない場合の処理を書けばよいかも)
後は、ターミナルなどで実行する。
$rails db:seed
追記 ifを使った改良例
result['quotes'].each do |key, value|
if Exchange.find_by(currency: key)
rate = Exchange.find_by(currency: key)
rate.update(rate: value)
else
Exchange.create(currency: key, rate: value)
end
end
#感想
seed.rbは簡単に扱えたが、やはりjsonの処理には少し苦戦した。
railsはAPIを簡単に自作できるらしいので、データベースに問い合わせるのではなくAPIを自作し、それを叩くという実装もしてみたい。
#追記
httpsのAPIを利用しようとすると、証明書が取得できないという以下の様なエラーが出ることがある。
SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
その場合はここから証明書(cacert.pem)をダウンロードして、プロジェクト内に配置する。
そして、スクリプトの最初に ENV["SSL_CERT_FILE"] ="app/controllers/cacert.pem"
(コントローラー内に配置する場合)を追記する。
Memo:ファイルの場所はプロジェクトパスで指定しないとうまくいかない。
(多くの参考サイトでは、./cacert.pem
と指定していたが、私の環境では相対パスではうまくいかなかった。
#追記(さらに改良)
sakuro様からコメントにてfind_or_initialize_by()という素晴らしい書き方のアドバイスいただいたので書き直しました!
今回は新規作成時と更新時で処理を分ける必要がなかったので、find_or_create_by()を使ってみました。
result['quotes'].each do |key, value|
rate = Exchange.find_or_create_by(currency: key)
rate.update(currency: key, rate: value)
end
if文が無くなり、コードがシンプルでカッコ良くなりました!
アドバイスありがとうございます!