Railsで外部APIを毎秒&永続的に値を取得し、DBに格納する

2 min read読了の目安(約2200字

まえおき

やりたいことはタイトル通りなのだが、実際にやってみると当初考えていたより苦戦したので、その時の作業メモをまとめておく。

環境

Rails: 6.1.0
Ruby: 2.7.2
Faraday: 0.17.4

結論

ruby のloop + sleepを用いたメソッドを作成し、rails runnerで実行する。
参考にした記事: 【Ruby】faradayを利用した外部API連携クラスの作成手順

やり方

1. メソッド作成

lib/hoge/fuga.rb を作成。中身は以下の通り。

class Hoge::Fuga < ApplicationRecord
  INTERVAL = 1
  URL = '外部APIのUTL'

  def self.get
    loop do
      sleep(INTERVAL)
      response = Faraday.get(URL)
      res = JSON.parse(response.body)
      res['created_at'] = Time.current
      Fuga.insert_all([res])
    end
  end
end

Tips

  • loop内でsleepを使うことで、引数に与えた秒数の間処理を一時停止させる。今回は毎秒にしたかったので引数(INTERVAL) を1にしている。環境変数をセットし、実行時に秒間を調整できるようにしてもいいかも。
  • 今回は取得した値をほぼそっくりそのままDBへ格納したかった。そのため、いちいち各カラムに値を入れ込むのが面倒だったので、ハッシュ形式のまま入れ込めるinsert_allを使用した。
    • ちなみにinsert_allではcreated_atupdate_atを自分で入れ込まなければならないので、res['created_at'] = Time.currentを記述している。
  • 外部APIとのやりとりはFaradayを使うと便利。ドキュメントも充実してておすすめ。
  • エラー処理などはここでは記述してないので、別途記述が必要。

2. configにパスを追記

libはRailsで自動的にロードされないため、config/application.rbに以下を追記。

config.autoload_paths += %W(#{config.root}/lib)

3. runnerで実行

bin/rails runner Hoge::Fuga.get で実行

採用しなかったこと

1. whenever

秒単位の設定ができなかったため採用しなかった。1秒毎にAPIを叩くrunnerを作成しそれを1分毎に実行する、とすれば一応やりたいことは実現できるが、なんとなく微妙だなと思ってやめた。
似たようなことをwheneverでするにはどうすればいいかという質問がstackoverflowに投稿されていたが、その中では「wheneverではできないけど、シェルスクリプトを使えばいけるぜ」と回答されていた。

https://stackoverflow.com/questions/37112211/whenever-gem-is-not-working-for-every-2-seconds

2. 1秒毎に実行されるrake task をDaemonで実行する

以下の記事を参考にやってみようと思ったが、紹介されているdaemon-spawngemというgemがほとんど更新されていなかったため採用しなかった。

https://llcc.hatenablog.com/entry/2018/04/26/101550

まとめ

とりあえず今回のやり方でタイトル通りのことはできるようになった。ただしエラー処理は考慮してないため、それは別途書く必要があると思う。また本番環境では無理にrunnerを動かすより、awsのlamdaなどで定期実行させた方がいいかもしれない。もしやってる方がいたら、ぜひコメントで教えてください。
またもっといいやり方があるよとか、うちではこういう風にしてるよとかあれば是非コメントよろしくお願いします。

参考資料

https://nishinatoshiharu.com/faraday-apiclient/
https://qiita.com/rllllho/items/672e336a03335cba6b34
https://doruby.jp/users/red/entries/rails___model___daemon__