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

# 概要
LINEでyoutubeやamazonのリンクを送信すると、そのサムネイル画像やタイトルを自動で表示してくますよね？<br>あれはOGPと言ってurlからそのページに設定されているタイトル、url、概要、画像などの情報を取得する仕組みを利用しています。<br>javascriptを使ってOGPを取得する記事はたくさんありますが、railsで取得した値をDBに保存するやり方が書かれた記事がなかったので、書いてみました！
## コントローラーとテーブル
コントローラーはpostsコントローラーでアクションはindex, new, create, showの4つ。<br>テーブルはpostsテーブルで、カラムは以下のようになります。

![db.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988077/5dfdabf0-2c23-6116-61a9-fe2084f00f7e.jpeg)
図のようにbody, urlカラムは実際にユーザーが入力するためのカラム、<br>url_image, url_titleカラムはOGPから自動で取得して保存するカラムになります。newアクションでユーザーが投稿をする際にOGPからデータを取得して保存することで、一覧ページや詳細ページで表示することができます。
# 完成図
![自動入力動画_4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988077/16c2a3c1-5ab6-e168-68a1-2602935a296a.gif)

# 開発環境
>- ruby '3.0.3'
>- rails '6.1.5'
# 実装
それでは実装していきましょう！
## 投稿機能を完成させる
まずは一般的にな投稿機能を完成させていきます。すでにできている人は基本的には飛ばしていただいで大丈夫なのですが、new.html.erbでフォームと入力欄にid名をつけていますので、そこだけコードと同じになるように修正してください。<br>
(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)コントローラーにアクションを定義する
```ruby: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ページを作成する
```erb: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>
```
```erb: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 %>
```
```erb: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
```
以上で一般的な投稿機能ができたかと思います！
<h2>urlからOGPを取得して保存する</h2>
ここからは実際にjavascriptでOGPを取得していきましょう！<br>今回はLinkpreviewというAPIを利用します
<h3>Linkpreviewの会員登録</h3>
<br><br>まず下記urlから会員登録をしてください！

[https://www.linkpreview.net/](https://www.linkpreview.net/)

【sign_up】ボタンから諸情報を入力すると設定したメールアドレスに認証メールが届くので送信されたurlをクリックして登録を完了させてください！<br><br>
以下のようにダッシュボードが表示されば完了です。

![linkpreview.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988077/7e854acc-b7d0-80d2-cb62-e6e106671942.jpeg)

以下のサイトを参考にさせていただいので、出来ない方は見てみてください。
>[JavaScriptやNoCodeからリンクのプレビュー機能を開発できる「LinkPreview」を使ってみた！](https://paiza.hatenablog.com/entry/2020/08/27/JavaScript%E3%82%84NoCode%E3%81%8B%E3%82%89%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%AE%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E6%A9%9F%E8%83%BD%E3%82%92%E9%96%8B%E7%99%BA%E3%81%A7%E3%81%8D%E3%82%8B)
<h3>OGPの取得と保存</h3>
それでは前準備が完了したのでいよいよ、OGPの取得に入りましょう。
以下のコードをnewページの<b>最後に</b>コピペしてください。他にjavascriptを記入するファイルを用意している方はそちらでも大丈夫かと思います。<br>
key: '**************************'
の部分には先ほどのLink_previewのダッシュボードに表示されたAPI keyを入力して下しい。

```ruby: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988077/dde996c4-07cd-049d-5da9-17523c1468d2.gif)


## 細かな見た目の変更
以上で、OGPからデータを取得して保存は完了しているのですが、これでは

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

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

### url_image, url_titleという入力欄を消す
消したいtext_fieldにクラス名を追加してcssで```display: none;```をかけましょう
```erb: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>
```
```css:app/assets/stylesheets/posts.cscc
/* 追記 */
.display-none {
    display: none;
}
```
これで必要ない入力欄を消せたかと思います
### urlをリンクにする
urlをaタグに変更しましょう
```erb:app/views/posts/index.html.erb
#変更前
<p><%= post.url %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= post.url %>"><%= post.url %></a></p>
```
```html:app/views/posts/show.html.erb
#変更前
<p><%= @post.url %></p>
---------------------------------------------------------------------------
#変更後
<p><a href="<%= @post.url %>"><%= @post.url %></a></p>
```
### urlを消してurl_titleをリンクにする
```erb: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>
```
```html: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のコードだけ解説をしておきます。
```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();```にてデフォルトの送信機能が実行されないようにしています。
```javascript: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の入力欄に値を入力しています。
```javascript:app/views/posts/new.html.erb
setTimeout(function() {
    myform.submit();
}, 1000);
```
最後にこの部分で、フォームの送信部分を行なっております。ただ、```myform.submit()```をするだけだと、処理の順番が追い越されて、OGPを取得する前にフォームが送信されてしまったので、```setTimeout```を利用して1秒後に送信されるようにしています。より綺麗な処理の仕方があるとは思っているので、何かアイデアがある方は是非教えてください。
# 参照
以下、参考にさせていただいたサイトと本のリンクになります。
>- [JavaScriptやNoCodeからリンクのプレビュー機能を開発できる「LinkPreview」を使ってみた！](https://paiza.hatenablog.com/entry/2020/08/27/JavaScript%E3%82%84NoCode%E3%81%8B%E3%82%89%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%AE%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E6%A9%9F%E8%83%BD%E3%82%92%E9%96%8B%E7%99%BA%E3%81%A7%E3%81%8D%E3%82%8B)
>- [LinkPreview](https://www.linkpreview.net/)
>- [いまさら聞けない！OGPとは？](https://www.e-webseisaku.com/column/marketing/3947/)
>- [JavaScript コードレシピ集](https://www.amazon.co.jp/JavaScript-%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AC%E3%82%B7%E3%83%94%E9%9B%86-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BEICS-%E6%B1%A0%E7%94%B0%E6%B3%B0%E5%BB%B6-ebook/dp/B07N8YVCRQ/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=1T8JKUJXRMXBA&keywords=javascript+%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89&qid=1671919590&sprefix=javascript+%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89%2Caps%2C318&sr=8-1)
>- [【MAD】僕のヒーローアカデミア トゥワイス それを愛と呼ぶなら](https://www.youtube.com/watch?v=XW5h_-WT2tk)