3行まとめ
- ゆるふわRails野郎がFacebookGraphAPIをつかってFacebookの投稿を取得してみた
- Ruby(Rails)でFacebookGraphAPIさわる情報がなかったので書いてみた
- Railsで超おもしろサービスつくっているけれど、まわりにRails相談できる環境があまりなく、よい設計ではない気もするのでご意見いただきたく晒してみる*1
前提
- FacebookGraphAPI v2.8
- Ruby2.3.3、Rails4.2
やりたいこと
Railsで動いているWebアプリ上でFacebookの投稿を表示したい。
背景
- ブログ/ニュースフィードを車輪の再発明したくないし、アプリ上とFacebook上で2重更新したくない(読み手が違うので分けるメリットはあるけれど)
- ページプラグイン - ソーシャルプラグイン - ドキュメンテーション - 開発者向けFacebookは最大幅500pxという制約があって使えない
やったこと
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にチェックをいれる。
たとえば、postを取得するのであれば、APIのバーに以下のように設定して送信を押す。うまくいくと、取得されたjsonが表示されるはず。
{アプリ名}/feed
RailsでAPIをたたく
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:エンジニアリング力(ちから)をあげておもしろサービスをよりおもしろくしていきたい