読者です 読者をやめる 読者になる 読者になる

うんこめも

自律分散うんこ製造システムについてのメモ

RailsでFacebookAPIを叩いてみる

3行まとめ

  • ゆるふわRails野郎がFacebookGraphAPIをつかってFacebookの投稿を取得してみた
  • Ruby(Rails)でFacebookGraphAPIさわる情報がなかったので書いてみた
  • Railsで超おもしろサービスつくっているけれど、まわりにRails相談できる環境があまりなく、よい設計ではない気もするのでご意見いただきたく晒してみる*1

前提

  • FacebookGraphAPI v2.8
  • Ruby2.3.3、Rails4.2

やりたいこと

Railsで動いているWebアプリ上でFacebookの投稿を表示したい。

背景

やったこと

FacebookGraphAPIのAppトークンを取得

Facebook for developpersにアクセスしてアプリを追加する。 https://developers.facebook.com/apps/

ユーザトークンを取得する(ここで取得するトークンは有効期限が1時間なので注意) https://developers.facebook.com/tools/accesstoken/

facebook graph api exploreでAPIを試してみる(めっちゃ便利、だけれどドキュメント多くてなにでなにができるかわかりにくい)

https://developers.facebook.com/tools/explorer

トークンのアイコンをクリックして、user_tokenを取得を選択し、許可を与える項目をチェックする。たとえば投稿を取得するのであればuser_postsにチェックをいれる。 f:id:daaaaaai:20170320220812p:plain

たとえば、postを取得するのであれば、APIのバーに以下のように設定して送信を押す。うまくいくと、取得されたjsonが表示されるはず。

{アプリ名}/feed

RailsAPIをたたく

model以下に素のrubyファイルをつくる。

class FacebookViewer

  # 一般的なメソッドなのでほかに使うものがあればヘルパークラスに移す。
  # この記事を参考にした(コピペ)
  # http://qiita.com/awakia/items/bd8c1385115df27c15fa
  def self.get_json(location, limit = 10)
    raise ArgumentError, 'too many HTTP redirects' if limit == 0
    uri = URI.parse(location)
    begin
      response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
        http.open_timeout = 5
        http.read_timeout = 10
        http.get(uri.request_uri)
      end
      case response
      when Net::HTTPSuccess
        json = response.body
        JSON.parse(json)
      when Net::HTTPRedirection
        location = response['location']
        warn "redirected to #{location}"
        get_json(location, limit - 1)
      else
        puts [uri.to_s, response.value].join(" : ")
        # handle error
      end
    rescue => e
      puts [uri.to_s, e.class, e].join(" : ")
      # handle error
    end
  end

  # FacebookGraphAPIからkeyを含む項目を取得する。
  def self.get_fb_items(key)
    # API spec
    # https://developers.facebook.com/docs/graph-api/reference/v2.8/page/feed
    result_array = []

    begin
      accecc_token = ENV.fetch('FB_ACCESS_TOKEN')
      fb_posts_json = get_json("https://graph.facebook.com/v2.8/#{アプリ名}/feed?access_token=#{accecc_token}")

      # FacebookGraphAPIからはdata以下に各項目のhashがはいったjsonが返ってくる
      fb_posts_json['data'].each { |content|
        result_array.push(content) if content.key?(key)
      }
    rescue => e
      p e.message
    end

    return result_array
  end
end

クラスメソッドばかりだけれどこんな感じです。 エラー処理が二重になっていてあまりよくない気もする・・・。

この最後のメソッドをcontrollerでよんでインスタンス変数としてviewに渡す。さらにパーシャルのfb_posts変数に渡すとこうなるかな。 (実際には日付表示やsimple_formatあたりはhelperに書いたものを使っています。とにかくtruncateメソッド便利。)

<% fb_posts.each{ |post| %>
<dl class="newsList_item">
  <dt class="date"><%= DateTime.parse(post['created_time']).strftime("%Y.%m.%d") %></dt>
  <dd class="text"><%= simple_format(post['message'].truncate(160, omission: link_to("(もっと見る)",'https://www.facebook.com/'+ post['id']))) %></dd>
</dl>
<% } %>

テストを書きます(テストファースト、ふだんもあまりできていないけれど、仕様を手探りするAPI叩く系だともっとできない)。 ふだんからテスト書くの迷うけれどAPIのテストを書くのはより悩む。 テストで本番のAPIサーバ叩くのは望ましくない気がする、そもそもローカルの環境変数にいれている本番のaccess_tokenつかうのも不穏。

けれどFacebookさまのAPIということと、テスト頻度もぼちぼちということ、そして本番のAPIにアクセスできないことをいちはやく検知したいということでテストでも本番にアクセスしてみている。 (失敗した場合書けていないしもっとよいやりかたありそうな気がするのでご指摘いただければ幸いです・・・)。

require 'test_helper'

class FacebookViewerTest < ActiveSupport::TestCase
  test "should get response" do
    result_hash = FacebookViewer.get_fb_items_with_cache('message')
    assert result_hash.size > 0
    assert result_hash[0].has_key?('id')
    assert result_hash[0].has_key?('created_time')
    assert result_hash[0].has_key?('message')
  end
end

あと、Herokuを使っている場合は環境変数を設定するのを忘れずに。

heroku config:set FB_ACCESS_TOKEN=‘トークン文字列’ –app アプリ名

Railsでキャッシュを設定する

さて、これでなんとか動くしテストも通っています。ただ、100人がアクセスしたら100回API叩きにいくというのはいけていないですね。 とりあえずキャッシュします。

Rails のキャッシュ: 概要 | Rails ガイドです。

  # 20分は適当・・・
  def self.get_fb_items_with_cache(key)
    Rails.cache.fetch("facebook_#{key}", expires_in: 20.minutes) do
      get_fb_items(key)
    end
  end

20分というのは適当です。

わからなかったこと

こういう、ActiveRecordとあまり関係がないクラスをRailsのどこにおくか。 とりあえずmodelにおいた。MVC的にはMっぽいけれど、MのなかでActiveRecordを継承しているものとそうでないものが混在しているのはちょっと気持ち悪さもある。DDD的にはアプリケーション層のサービスっぽい気もするけれど、それをRailsのファイルパスでどう表現するべきだろうか。

Herokuでの環境変数の管理。 とりあえず新しい環境変数を使うことになったら、ローカルの.bash_profileにexportを書いて、Heroku上で変数セットして、Wikiに書くとやっているけれど、チーム開発で共有するときはどうするんだろう。config/environments/development.rbに書くといいんだろうか?

技術メモの書き方。わりと迷いがあってやってみたメモなのでQiitaではなく個人ブログに書いてみた。 こういう記事を書きやすくするためには、なにか新しいことに取り組むときや、問題を解決するときには手順メモを書きながらやるべき。ググりながら試行錯誤していつのまにかうまくいっているみたいなのやりがちだけれど、未来の自分が困るし知見を共有できないのでメモをとろう。

*1:エンジニアリング力(ちから)をあげておもしろサービスをよりおもしろくしていきたい