すること
今回はGET statuses/oembed — Twitter Developersを用いてツイッターカードを表示します。
埋め込みHTMLを取得するためには認証は必要ないので、単純なリクエストでデータを取得することが可能です。
流れは以下の通りです。
- 入力されたURLからIDを取得
- リクエストを送信してデータを取得
- URL, jsonをデータベースに保存
- 保存したデータを表示
開発環境
Ruby 2.6.5
Rails 5.2.3
MySQL 8.0.19
コントローラ
別段珍しいことはしていません。
redirect_to, renderは適当です。
class TweetsController < ApplicationController
def create
if @tweet.create(tweet_params)
redirect_to tweet_path(@tweet)
else
render :new
end
end
def update
@tweet = Tweet.find(params[:id])
if @tweet.update(tweet_params)
redirect_to tweet_path(@tweet)
else
render :edit
end
end
private
def tweet_params
params.require(:tweet).permit(:url)
end
end
モデル
事前に、jsonを入れる予定のt_json
カラムをマイグレーションで作成しておきます。
(追記)jsonを1カラムを入れるのは、変更やデータの取得がしづらいためアンチパターンと言われています。使う際は注意してください。
# == Schema Information
#
# Table name: tweets
#
# id :bigint not null, primary key
# url :string(255) not null
# t_json :json not null
# created_at :datetime not null
# updated_at :datetime not null
#
class Tweet < ApplicationRecord
validates :url, presence: true, length: { maximum: 200 }
end
このままでは、入力したurlしか保存されません。ということで、urlからt_jsonを取得し保存するまでの流れを順に追っていきます。
1. 入力されたURLからIDを取得
URLは、例えばhttps://twitter.com/Interior/status/507185938620219395
という形になりますが、リクエストに必要なのはIDの507185938620219395
の部分だけです。
ということで、入力されたURLからIDとなる文字列を抜き出すtweet_id
というインスタンスメソッドを作成します。
def tweet_id
url.split('/').last
end
ちなみに、tweet.tweet_id
というような書き方で、モデルの外部からもIDを取得できます。
2. リクエストを送信してデータを取得
TwitterAPIにGETリクエストを送ります。
curlコマンドでは以下のようなコマンドになります。
$ curl 'https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2FInterior%2Fstatus%2F507185938620219395'
これをRubyで実装します。2つの方法を挙げます。
open_uriを用いる方法
def tweet_json
url = "https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2FInterior%2Fstatus%2F#{tweet_id}"
responce = OpenURI.open_uri(url) # リクエストを送り、返り値をresponceに代入
ActiveSupport::JSON.decode(responce.read)
end
Net::HTTPを用いる方法
def tweet_json
url = "https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2FInterior%2Fstatus%2F#{tweet_id}"
uri = URI.parse(url)
responce = Net::HTTP.get(uri) # リクエストを送り、返り値をresponceに代入
JSON.parse(responce)
end
Ruby の HTTP リクエストを送る方法の性能比較によると、Net::HTTPの方が速いようです。
これで以下のようなjson形式のデータを返すメソッドを作成できました。
{
"url": "https://twitter.com/Interior/status/507185938620219395",
"author_name": "US Dept of Interior",
"author_url": "https://twitter.com/Interior",
"html": "<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Happy 50th anniversary to the Wilderness Act! Here's a great wilderness photo from <a href="https://twitter.com/YosemiteNPS">@YosemiteNPS</a>. <a href="https://twitter.com/hashtag/Wilderness50?src=hash">#Wilderness50</a> <a href="http://t.co/HMhbyTg18X">pic.twitter.com/HMhbyTg18X</a></p>— US Dept of Interior (@Interior) <a href="https://twitter.com/Interior/status/507185938620219395">September 3, 2014</a></blockquote>n<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>",
"width": 550,
"height": null,
"type": "rich",
"cache_age": "3153600000",
"provider_name": "Twitter",
"provider_url": "https://twitter.com",
"version": "1.0"
}
3. 取得したjsonをtweet.t_json
に格納して保存
tweetを保存する前に、before_save
を用いてt_json
に値を入れることにします。
set_json
メソッドの役割は、URLが変更されていた場合、t_json
に値を入れるというものです。
外部APIを叩きすぎるのはよくないので、URLが変わっていない場合は特に変更しません(今回の例では他に変更しうるカラムがないので別に必要ないのですが)。
class Tweet < ApplicationRecord
before_save :set_json # 追加
def set_json
self.t_json = tweet_json if will_save_change_to_url?
end
def tweet_json
# TwitterAPIから取得したJSONデータを返す
end
end
最終的なモデル
class Tweet < ApplicationRecord
before_save :set_json
validates :url, presence: true, length: { maximum: 200 }
def tweet_id
url.split('/').last
end
private # 以下のメソッドは外部から呼び出さないのでprivateにする
def set_json
self.t_json = tweet_json if will_save_change_to_url?
end
def tweet_json
url = "https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2FInterior%2Fstatus%2F#{tweet_id}"
uri = URI.parse(url)
responce = Net::HTTP.get(uri)
JSON.parse(responce)
end
ビュー
Twitterカードを表示するにはjsonの"html"
をそのままHTMLとして出力すればいいので、ビューファイルで以下のように記述します。
<%= @tweet.t_json['html'].html_safe %>
ツイート情報を取得する際のオプション
"https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2FInterior%2Fstatus%2F#{tweet_id}&omit_script=true"
このように&
でつなげます。(パラメータの種類については公式ページ参照)
omit_script=true
にすると、<script>
部分を除いたHTMLが返されるので、1ページに複数のツイートを表示する場合はおすすめです。
<% @tweets.each do |tweet| %>
<%= tweet.t_json['html'].html_safe %>
<% end %>
<%= javascript_include_tag '//platform.twitter.com/widgets.js' %>
リンク
例外処理など細かく行う場合