概要
LINEでyoutubeやamazonのリンクを送信すると、そのサムネイル画像やタイトルを自動で表示してくますよね?
あれはOGPと言ってurlからそのページに設定されているタイトル、url、概要、画像などの情報を取得する仕組みを利用しています。
javascriptを使ってOGPを取得する記事はたくさんありますが、railsで取得した値をDBに保存するやり方が書かれた記事がなかったので、書いてみました!
コントローラーとテーブル
コントローラーはpostsコントローラーでアクションはindex, new, create, showの4つ。
テーブルはpostsテーブルで、カラムは以下のようになります。
図のようにbody, urlカラムは実際にユーザーが入力するためのカラム、
url_image, url_titleカラムはOGPから自動で取得して保存するカラムになります。newアクションでユーザーが投稿をする際にOGPからデータを取得して保存することで、一覧ページや詳細ページで表示することができます。
完成図
開発環境
- ruby '3.0.3'
- rails '6.1.5'
実装
それでは実装していきましょう!
投稿機能を完成させる
まずは一般的にな投稿機能を完成させていきます。すでにできている人は基本的には飛ばしていただいで大丈夫なのですが、new.html.erbでフォームと入力欄にid名をつけていますので、そこだけコードと同じになるように修正してください。
(1)コントローラーを作成する
$ rails g controller posts
(2)モデルとテーブルを作成する
$ rails g model post body:text url:text url_image:text url_title:string
$ rails db:migrate
(3)コントローラーにアクションを定義する
class PostsController < ApplicationController
def index
@posts = Post.all
end
def new
@post = Post.new
end
def create
post = Post.new(tweet_params)
if post.save
redirect_to :action => "index"
else
redirect_to :action => "new"
end
end
def show
@post = Post.find(params[:id])
end
private
def post_params
params.require(:post).permit(:body, :image, :image_url, :image_title)
end
end
(4)viewページを作成する
<h1>投稿一覧</h1>
<% @posts.each do |post| %>
<p><%= post.body %></p>
<p><%= post.url %></p>
<p><%= post.url_title %></p>
<%= image_tag post.url_image, size: "100x100" if post.url_image? %>
<%= link_to "詳細へ", post_path(post.id) %>
<% end %>
<p><%= link_to "新規投稿へ", new_post_path %></p>
<%= form_with(model: @post, id: "myform") do |f| %>
<div class="field">
<%= f.label :body %>
<%= f.text_field :body, :size => 140 %>
</div>
<div class="field">
<%= f.label :url %>
<%= f.text_field :url, :size => 140, id: "url" %>
</div>
<div class="field">
<%= f.label :url_image %>
<%= f.text_field :url_image, :size => 140, id: "url_image" %>
</div>
<div class="field">
<%= f.label :url_title %>
<%= f.text_field :url_title, :size => 140, id: "url_title" %>
</div>
<%= f.submit "投稿する" %>
<% end %>
<%= link_to "一覧に戻る", posts_path %>
<p><%= @post.body %></p>
<p><%= @post.url %></p>
<p><%= @post.url_title %></p>
<%= image_tag @post.url_image, size: "100x100" if @post.url_image? %>
<%= link_to "一覧へ", posts_path %>
(5)ルーティング
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :posts
root 'posts#index'
end
以上で一般的な投稿機能ができたかと思います!
urlからOGPを取得して保存する
ここからは実際にjavascriptでOGPを取得していきましょう!今回はLinkpreviewというAPIを利用します
Linkpreviewの会員登録
まず下記urlから会員登録をしてください!
【sign_up】ボタンから諸情報を入力すると設定したメールアドレスに認証メールが届くので送信されたurlをクリックして登録を完了させてください!
以下のようにダッシュボードが表示されば完了です。
以下のサイトを参考にさせていただいので、出来ない方は見てみてください。
OGPの取得と保存
それでは前準備が完了したのでいよいよ、OGPの取得に入りましょう。 以下のコードをnewページの最後にコピペしてください。他にjavascriptを記入するファイルを用意している方はそちらでも大丈夫かと思います。key: '**************************' の部分には先ほどのLink_previewのダッシュボードに表示されたAPI keyを入力して下しい。
#viewページの最後にコピペ
<script>
window.addEventListener("turbolinks:load", function(){
var myform = document.getElementById("myform");
myform.addEventListener("submit", function(e){
e.preventDefault();
const url = document.getElementById("url").value;
const data = {
key: '**************************',
q: url
}
fetch('https://api.linkpreview.net', {
method: 'POST',
mode: 'cors',
body: JSON.stringify(data),
})
.then(data => data.json())
.then(json => {
document.getElementById("url_image").value = json.image;
document.getElementById("url_title").value = json.title;
});
setTimeout(function() {
myform.submit();
}, 1000);
});
});
</script>
以上で完了になります。urlを入力して投稿ボタンを押すと、画像とタイトルが自動で取得されて投稿されるようになったと思います!!
細かな見た目の変更
以上で、OGPからデータを取得して保存は完了しているのですが、これでは
- url_image, url_titleという入力欄が存在してユーザーが記入しなければならないと誤解しそう
- urlがリンクになっておらず、サイトに飛べない
- amazonの商品などの長いリンクがそのまま表示されるため、ダサい
などの問題がありそうなので、それぞれ解決していきましょう。
url_image, url_titleという入力欄を消す
消したいtext_fieldにクラス名を追加してcssでdisplay: none;
をかけましょう
#変更前
<div class="field">
<%= f.label :url_image %>
<%= f.text_field :url_image, :size => 140, id: "url_image" %>
</div>
<div class="field">
<%= f.label :url_title %>
<%= f.text_field :url_title, :size => 140, id: "url_title" %>
</div>
---------------------------------------------------------------------------
#変更後
<div class="field display-none">
<%= f.label :url_image %>
<%= f.text_field :url_image, :size => 140, id: "url_image" %>
</div>
<div class="field display-none">
<%= f.label :url_title %>
<%= f.text_field :url_title, :size => 140, id: "url_title" %>
</div>
/* 追記 */
.display-none {
display: none;
}
これで必要ない入力欄を消せたかと思います
urlをリンクにする
urlをaタグに変更しましょう
#変更前
<p><%= post.url %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= post.url %>"><%= post.url %></a></p>
#変更前
<p><%= @post.url %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= @post.url %>"><%= @post.url %></a></p>
urlを消してurl_titleをリンクにする
#変更前
<p><a href="<%= post.url %>"><%= post.url %></p>
<p><%= post.url_title %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= post.url %>"><%= post.url_title %></a></p>
#変更前
<p><a href="<%= @post.url %>"><%= @post.url %></p>
<p><%= @post.url_title %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= @post.url %>"><%= @post.url_title %></a></p>
解説
javascriptのコードだけ解説をしておきます。
window.addEventListener("turbolinks:load", function(){
var myform = document.getElementById("myform");
myform.addEventListener("submit", function(e){
e.preventDefault();
//省略
});
});
まず、この部分について、フォームのサブミットボタンが押された際に反応して処理が実行されるようにしています。その際にOGPを取得する前に送信が実行されてはまずいので、e.preventDefault();
にてデフォルトの送信機能が実行されないようにしています。
const url = document.getElementById("url").value;
const data = {
key: '**************************',
q: url
}
fetch('https://api.linkpreview.net', {
method: 'POST',
mode: 'cors',
body: JSON.stringify(data),
})
.then(data => data.json())
.then(json => {
document.getElementById("url_image").value = json.image;
document.getElementById("url_title").value = json.title;
});
続いて、この部分でlinkpreview_APIを叩いて、画像のurlやタイトルを取得して、url_imageとurl_titleの入力欄に値を入力しています。
setTimeout(function() {
myform.submit();
}, 1000);
最後にこの部分で、フォームの送信部分を行なっております。ただ、myform.submit()
をするだけだと、処理の順番が追い越されて、OGPを取得する前にフォームが送信されてしまったので、setTimeout
を利用して1秒後に送信されるようにしています。より綺麗な処理の仕方があるとは思っているので、何かアイデアがある方は是非教えてください。
参照
以下、参考にさせていただいたサイトと本のリンクになります。