Nuxt.js x Ruby on Rails で画像ファイルを含む内容を保存させるときにやること

## [はじめに](#-はじめに)
フロントエンドにNuxt.js、バックエンドにRails APIモードでの画像ファイルを含む内容を保存させるときにやることの記事です。
Nuxt.js、Railsはそれぞれ構築済みであることとします。

## [必要なもの](#-必要なもの)
`gem carrierwave`

## [実装方法](#-実装方法)
### Rails
１. Gemfileに`carrierwave`を追記しターミナルで`bundle install`します。
```Ruby:Gemfile
...途中省略
gem 'carrierwave', '~> 2.0'
```

```Ruby:ターミナル
bundle install
```
２. アップローダークラスの生成
ターミナルで下記コマンドを実行してアップローダークラスを生成します。
```ruby:ターミナル
bundle exec rails g uploader アップローダー名（大文字始まり）
```
実行すると、`/app/uploaders/アップローダー名_uploader.rb`が生成されます。
`Image`の場合は下記のようになります。
```ruby:ターミナル
bundle exec rails g uploader Image
```

３．アップロードする画像のカラムをテーブルに追加します。
アップロードする画像情報を保存するカラムをテーブルに追加するマイグレーションファイルを作成します。
ターミナルで下記コマンドを実行します。
```ruby:ターミナル
bundle exec rails g migration add_カラム名_to_テーブル カラム名:string 
```
マイグレーションファイルが作成されたら、ターミナルでマイグレーションを実行します。
```ruby:ターミナル
bundle exec rails db:migrate
```
追加したカラムをコントローラのプライベートメソッドに追記します。
```ruby:/app/controllers/v1/***_controllers.rb
module V1
  class ***Controller < ApplicationController
    ...途中省略
    def create
      *** = ***.create!(***_params)
      if ***.save
        render json: ***
      else
        render json: ***.errors
      end
    end

　　　　  private
    def ***_params
      params.require(:***).permit(:カラム名, :カラム名, :追加したカラム名)
    end
  end
end
```
４．アップローダークラスとカラムを紐付けます。
```ruby:/app/models/***.rb
class モデル名 < ApplicationRecord
  mount_uploader :追加したカラム名, アップローダークラス
end
```
５．.gitignoreに`/public/uploads`を追記します。
保存に成功した画像ファイルは`/public/uploads`に置かれるので、Git管理下から除外するため`.gitignore`に追記します。

### Nuxt.js
フロントエンドは親コンポーネントと子コンポーネントに分けます。
```Nuxt:/pages/index.vue
<template>
  <AddImage @submit="addImage" />
</template>

<script>
import AddImage from '~/components/AddImage'
import axios from '~/plugins/axios'
export default {
  compoponents: {
    AddImage,
  },
  methods: {
    async addImage(formData, config) {
      await axios.post('バックエンドエンドポイント', formData, config)
        .then(() => {})
        .catch(() => {})
    }
  }
}
</script>
```

```Nuxt:/components/AddImage.vue
<template>
  <v-form>
    <v-container>
      <v-row>
        <v-col
          cols="12"
          md="4"
        >
          <div>
            <v-file-input
              label="image"
              accept="image/*"
              v-model="addImage"
              prepend-icon="mdi-camera"
            />
          </div>
        </v-col>
        <v-col
          cols="12"
          md="4"
        >
          <v-btn
            @click="handleSubmit"
          >
            作成
          </v-btn>
        </v-col>
      </v-row>
    </v-container>
  </v-form>
</template>

<script>
export default {
  data () {
    return {
      addImage: []
    }
  },
  methods: {
    handleSubmit() {
      const formData = new FormData()
      formData.append('key', this.addImage)
      const config = {
        headders: {
          'content-type': 'multipart/form-data'
        }
      }
      this.$emit('submit', formData, config)
    }
  }
}
</script>
```
子コンポーネントの「作成」ボタンをクリックすると`hundleSubmit`が動き、emitを使用して子コンポーネントから親コンポーネントへデータを渡しています。

## [最後に](#-最後に)
参考させていただいた記事をまとめます。
[【Rails】 CarrierWaveチュートリアル](https://pikawaka.com/rails/carrierwave)
[Vuetifyのv-file-inputで画像を送信する方法](https://poppotennis.com/posts/vue-v-file-input-image)