【Vision API(LABEL_DETECTION)】seeds.rbにも反映させる

はじめに
---
今回は、先日初めて実装したGoogle Vision APIの機能について自身の備忘録として残しておきたいと思います。
本記事では、Google Cloud Platformの登録につきましては省略させて頂いておりますので、ご了承ください。
<br>

開発環境
---
ruby 2.6.3
Rails 5.2.4.4
<br>

Google Vision APIとは
---
Google社が提供している、画像解析ツールのことを指します。
画像の中に写りこんでいる文字やロゴを認識したり、不適切な画像でないか判断してくれたりと、種類は様々あります。
その中で、今回私が取り組んだのは、LABEL_DETECTIONというもので、画像を認識し自動でラベル（タグ）を発行してくれるという機能になります。
こちらを活用し、写真付きの投稿記事に自動でタグが付くよう実装しております。
<br>

見本
---
「Food」「Cake decorating」「Cake」の茶色の部分が今回作成していく自動生成されるラベルになります。
※デフォルト検出が英語になっており、日本語にて検出する方法は現在模索中です。
※また、cssにてデザインは作成しておりますが、今回デザインにつきましてのご説明は記載しておりませんので、ご了承くださいますようお願い致します。

<img width="300px" alt="スクリーンショット 2021-02-21 0.26.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/942338/bd1e1b3b-4b4d-205a-7dd6-08f4f6d8ecd3.png">


今回使用するモデル
---
※必要最低限のカラムのみで構成しております。

|モデル名     |カラム名|
|:--        |:--     |
|User       |id      |
|           |name    |

|モデル名|カラム名| 
|:--        |:--     |
|Tweet      |id      |
|           |user_id |
|           |image_id|
|           |title   |
|           |content |

|モデル名     |カラム名|
|:--        |:--     |
|Tag        |tweet_id|
|           |name    |
※こちらのTagモデルが、LABEL_DETECTIONの機能で発行されるタグ用のモデル。
そして、nameのカラムの部分が発行されるタグ名に当たります。
<br>

config/initializers/refile.rb
---
上記のディレクトリの階層に、refile.rbというファイルを作成します。

```config/initializers/refile.rb
Refile.backends['store'] = Refile::Backend::FileSystem.new('public/uploads/')
```
<br>

Gemfile
---
Google API keyの情報が必要になり、keyの値は個人情報になりますので、環境変数に置き換えて使用します。
そのため、以下のgemを追加しbundle installします。

```Gemfile.
gem 'dotenv-rails'
```
<br>

.env
---
もし、.envファイルを今回初めて作成されるようであれば、ご自身のアプリケーション直下に作成してください。
また、GitHubにpushする際に公開されてしまいますと、個人情報の流出に繋がりますので、必ず.gitignoreファイルに「/.env」を追記しましょう。

```..env
GOOGLE_API_KEY="ご自身のGOOGLE_API_KEYをコピペする"
```
```..gitignore
/.env
```
<br>

lib/vision.rb
---
上記のディレクトリの階層に、vision.rbというファイルを作成します。
基本的には、こちらをコピペしていただければ動作するかと思います。

```lib/vision.rb
require 'base64'
require 'json'
require 'net/https'

module Vision
  class << self
    def get_image_data(image_file)
      # APIのURL作成
      api_url = "https://vision.googleapis.com/v1/images:annotate?key=#{ENV['GOOGLE_API_KEY']}"

      # 画像をbase64にエンコード
      base64_image = Base64.encode64(open("#{Rails.root}/public/uploads/#{image_file.id}").read)

      # APIリクエスト用のJSONパラメータ
      params = {
        requests: [{
          image: {
            content: base64_image
          },
          features: [
            {
              type: 'LABEL_DETECTION'
            }
          ]
        }]
      }.to_json

      # Google Cloud Vision APIにリクエスト
      uri = URI.parse(api_url)
      https = Net::HTTP.new(uri.host, uri.port)
      https.use_ssl = true
      request = Net::HTTP::Post.new(uri.request_uri)
      request['Content-Type'] = 'application/json'
      response = https.request(request, params)
      response_body = JSON.parse(response.body)
      # APIレスポンス出力
      if (error = response_body['responses'][0]['error']).present?
        raise error['message']
      else
        # take(3)の部分が取り出すラベル数になります。
        # 必要に応じて変更してください。
        response_body['responses'][0]['labelAnnotations'].pluck('description').take(3)
      end
    end
  end
end
```
<br>

config/application.rb
---
先ほど作成したlib以下のファイルを読み込むために、以下の1行を追記してください。
config.paths.add 'lib', eager_load: true

```config/application.rb
module （アプリケーション名）
  class Application < Rails::Application
    config.load_defaults 5.2
    # 追加
    config.paths.add 'lib', eager_load: true
  end
end
```
<br>

tweet_controller.rb
---

```tweet_controller.rb
  def create
    tweet = Tweet.new(tweet_params)
    tweet.save
    # ラベルを作成するために追記
    tags = Vision.get_image_data(tweet.image)    
    tags.each do |tag|
      tweet.tags.create(name: tag)
    end
    redirect_to tweets_path
```
<br>

tweet/index.html.erb
---
最後にビューにラベルを取得するための記述を追加します。
※ 投稿した記事が保存される時にラベルは自動生成されるので、ビューで変更を加えるのはラベルを表示させたい画面のみになります。

```tweet/index.html.erb
<% @tweets.each do |tweet| %>
　<% tweet.tags.each do |tag| %>
　　# lib/vision.rbで指定した数分だけラベルを取得
　　<%= tag.name %>
　<% end %>
　<%= tweet.title %>
　<%= tweet.user.name %>
<% end %>
```
<br>

今回一番書きたかったポイント
---
長々と書いてしまいましたが、一番記事として残しておきたかった部分はこちらからになります...
こちらまでお目通しいただけた方がおりましたら、長々とお付き合いいただきありがとうございます。
<br>

seeds.rbでデータを作成しても、このままでは反映されない！
---

実はデプロイ後を想定して、seeds.rbには以下のような形で、数十件分のtweetを記述していました。
しかし、seeds.rbでデータを作成する際は、コントローラーもアクションも呼び出されないため、ラベルは発行されません。

```seeds.rb
# 一部抜粋
Tweet.create!(
　user_id: 1,
　title: "隣町のケーキ屋！",
　content:"おしゃれで可愛いケーキがいっぱい。",
　image: File.open('./app/assets/images/tiramisu.jpg')
)
```
<br>

解決方法
---
①まず、tweet.contoroller.rbに追記したラベル作成（４〜８行目）の部分を削除します。

②ラベル作成の記述をtweetモデルに記述します。
意味：
● after_create :create_tags
　　tweetが生成された後にtagsを生成する

● def create_tags以降
　　実際にラベルを生成する記述

（補足）
先ほどのコントローラーの記述では、vision_tagsの部分はtagsのみになっております。
変更している理由は以下の通りです。
開発環境では、全てtagsのみで問題なかったのですが、デプロイ後の本番環境でseeds.rbを読み込もうとすると、任意で命名してるtagsの部分と、テーブル名のtagsの部分（tags.create...)で判別がつかない事によりエラーの原因になってしまったようでした。
※現状、開発環境と本番環境での差異については詳しく分かっておりません。

```models/tweet.rb
belongs_to :user
has_many :tags, dependent: :destroy
attachment :image

# コールバックを使用
after_create :create_tags

def create_tags
  # Vision APIのLABEL保存の記述
  vision_tags = Vision.get_image_data(self.image)
  vision_tags.each do |tag|
    tags.create(name: tag)
  end
end
```

####● コールバック
何かのイベント発生時に毎回実行されるコードのことを指します。
「〜をする前に実行する」、「〜をした後に実行する」、これらを定義するものになります。
<br>

モデルとコントローラーどちらへの記述が良いのか
---
結論からいうと、モデルが良いそうです！
（メンターさんより教わった情報を参考にさせて頂いております。）
コントローラーはなるべくスマートに最低限にを意識し、モデルをうまく活用すると良いとご教授いただきました。

終わり
---
今回は以上になります。
最後の部分に繋げるべく、一から長々と執筆してしまいました。
大変長くなってしまい、読みづらい部分も多いかと思います。申し訳ございません。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になればと思います。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。