4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GeekSalonAdvent Calendar 2022

Day 25

URLからOGPを取得してDBに保存する

Last updated at Posted at 2022-12-25

概要

LINEでyoutubeやamazonのリンクを送信すると、そのサムネイル画像やタイトルを自動で表示してくますよね?
あれはOGPと言ってurlからそのページに設定されているタイトル、url、概要、画像などの情報を取得する仕組みを利用しています。
javascriptを使ってOGPを取得する記事はたくさんありますが、railsで取得した値をDBに保存するやり方が書かれた記事がなかったので、書いてみました!

コントローラーとテーブル

コントローラーはpostsコントローラーでアクションはindex, new, create, showの4つ。
テーブルはpostsテーブルで、カラムは以下のようになります。

db.jpg
図のようにbody, urlカラムは実際にユーザーが入力するためのカラム、
url_image, url_titleカラムはOGPから自動で取得して保存するカラムになります。newアクションでユーザーが投稿をする際にOGPからデータを取得して保存することで、一覧ページや詳細ページで表示することができます。

完成図

自動入力動画_4.gif

開発環境

  • ruby '3.0.3'
  • rails '6.1.5'

実装

それでは実装していきましょう!

投稿機能を完成させる

まずは一般的にな投稿機能を完成させていきます。すでにできている人は基本的には飛ばしていただいで大丈夫なのですが、new.html.erbでフォームと入力欄にid名をつけていますので、そこだけコードと同じになるように修正してください。

(1)コントローラーを作成する

terminal
$ rails g controller posts

(2)モデルとテーブルを作成する

terminal
$ rails g model post body:text url:text url_image:text url_title:string
$ rails db:migrate

(3)コントローラーにアクションを定義する

app/controllers/posts_controller
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ページを作成する

app/views/posts/index.html.erb
<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>
app/views/posts/new.html.erb
<%= 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 %>
app/views/posts/show.html.erb
<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)ルーティング

config/routes.rb
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から会員登録をしてください!

https://www.linkpreview.net/

【sign_up】ボタンから諸情報を入力すると設定したメールアドレスに認証メールが届くので送信されたurlをクリックして登録を完了させてください!


以下のようにダッシュボードが表示されば完了です。

linkpreview.jpg

以下のサイトを参考にさせていただいので、出来ない方は見てみてください。

JavaScriptやNoCodeからリンクのプレビュー機能を開発できる「LinkPreview」を使ってみた!

OGPの取得と保存

それでは前準備が完了したのでいよいよ、OGPの取得に入りましょう。 以下のコードをnewページの最後にコピペしてください。他にjavascriptを記入するファイルを用意している方はそちらでも大丈夫かと思います。
key: '**************************' の部分には先ほどのLink_previewのダッシュボードに表示されたAPI keyを入力して下しい。
app/views/posts/new.html.erb
#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を入力して投稿ボタンを押すと、画像とタイトルが自動で取得されて投稿されるようになったと思います!!自動入力動画_3.gif

細かな見た目の変更

以上で、OGPからデータを取得して保存は完了しているのですが、これでは

  • url_image, url_titleという入力欄が存在してユーザーが記入しなければならないと誤解しそう
  • urlがリンクになっておらず、サイトに飛べない
  • amazonの商品などの長いリンクがそのまま表示されるため、ダサい

などの問題がありそうなので、それぞれ解決していきましょう。

url_image, url_titleという入力欄を消す

消したいtext_fieldにクラス名を追加してcssでdisplay: none;をかけましょう

app/views/posts/new.html.erb
#変更前
    <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>
app/assets/stylesheets/posts.cscc
/* 追記 */
.display-none {
    display: none;
}

これで必要ない入力欄を消せたかと思います

urlをリンクにする

urlをaタグに変更しましょう

app/views/posts/index.html.erb
#変更前
<p><%= post.url %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= post.url %>"><%= post.url %></a></p>
app/views/posts/show.html.erb
#変更前
<p><%= @post.url %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= @post.url %>"><%= @post.url %></a></p>

urlを消してurl_titleをリンクにする

app/views/posts/index.html.erb
#変更前
<p><a href="<%= post.url %>"><%= post.url %></p>
<p><%= post.url_title %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= post.url %>"><%= post.url_title %></a></p>
app/views/posts/show.html.erb
#変更前
<p><a href="<%= @post.url %>"><%= @post.url %></p>
<p><%= @post.url_title %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= @post.url %>"><%= @post.url_title %></a></p>

解説

javascriptのコードだけ解説をしておきます。

app/views/posts/new.html.erb
    window.addEventListener("turbolinks:load", function(){

        var myform = document.getElementById("myform");
        
        myform.addEventListener("submit", function(e){
            e.preventDefault();

            //省略
        });
    });

まず、この部分について、フォームのサブミットボタンが押された際に反応して処理が実行されるようにしています。その際にOGPを取得する前に送信が実行されてはまずいので、e.preventDefault();にてデフォルトの送信機能が実行されないようにしています。

app/views/posts/new.html.erb
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の入力欄に値を入力しています。

app/views/posts/new.html.erb
setTimeout(function() {
    myform.submit();
}, 1000);

最後にこの部分で、フォームの送信部分を行なっております。ただ、myform.submit()をするだけだと、処理の順番が追い越されて、OGPを取得する前にフォームが送信されてしまったので、setTimeoutを利用して1秒後に送信されるようにしています。より綺麗な処理の仕方があるとは思っているので、何かアイデアがある方は是非教えてください。

参照

以下、参考にさせていただいたサイトと本のリンクになります。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?