【Rails】画像投稿機能を実装する

Gem carrierwave(Webアプリケーションにファイルアップロード機能を提供するGem)を使用する。


## アップローダーとは
・アップローダーとはクラスオブジェクトである。
・アップローダーから生成されたオブジェクトを使って、ファイル名やファイルの保存先を取得できる。
・アップローダーにはファイルアップロードに関する設定を書ける

## 実装の流れ
1. アップローダーを作成
2. アップローダーをモデルで使用するように宣言
3. アップロードの設定を各ファイルで行う

## 達成したいこと
▪️掲示板に画像を設定できること
▪️サムネイルと表示されたラベルをクリックすると、ファイル選択画面が表示されること
▪️掲示板入力フォームで画像のみが未入力で作成ボタンを押した場合でも、掲示板一覧画面にリダイレクトして「掲示板を作成しました」というフラッシュメッセージが表示されること
▪️carrierwaveで使うカラムはBoardモデルにboard_image という名前で追加
▪️画像ファイルを登録する際に
→・画像は必須項目に含めない
　 ・アップロードできるファイルは jpg, jpeg, png, gif のみに制限する
　 ・画像が登録されていない掲示板に関しては、 board_placeholder.png が表示されるようにする
▪️バリデーションエラーで保存されなくても画像のキャッシュが残って再度ファイルを添付しなくてもいいようにする



## 手順
1. Gem carrierwaveのインストール
1. rails g uploader コマンドでavatarアップローダーの作成
1. アップローダーファイルapp/uploaders/board_image_uploader.rbに必要な記載（デフォルト画像、許可する画像形式）を追加
1. マイグレーションを行い画像用のカラムをDBに追加
1. アップローダーをカラムと関連付ける
1. boardsコントローラにストロングパラメータ、キャッシュの記載を追記
1. 掲示板投稿フォームのビューファイルに画像投稿用のフィールド＆キャッシュ用のフィールドを追加する
1. 画像投稿用カラムの項目を日本語表記にしたいので、ja.ymlファイルに翻訳を追記。
1. 掲示板の部分テンプレートに、アップロードした画像のURLを指定

gemファイルに以下の記載を追加。
```php:Gemfile
gem 'carrierwave', '~> 2.2.2'
```

ターミナルで以下を実行
```php:ターミナル
docker compose run web bundle install
```
gemを読み込むためにサーバーを再起動するのを忘れずに！

掲示板の画像用のアップローダークラスを作成するので、board_imageを指定してアップローダークラスを作成する。

```php:ターミナル
rails generate uploader BoardImage
```
app/uploaders/board_image_uploader.rbが作成される。

作成したアップローダークラス（BoardImageUploader）では、アップロードする画像の拡張子やサイズ、保存するパスを指定することができる。

デフォルトで「storage :file」が指定されているので、アップロードした画像はpublic/配下に保存される。
保存されるディレクトリは「store_dir」で設定される。
（保存されるのは画像ファイルそのものではなく参照データ）
（アップロードされた画像ファイルが"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"の形式で保存されることを示している）

### app/uploaders/board_image_uploader.rbを下記のように編集
```php:app/uploaders/board_image_uploader.rb
class BoardImageUploader < CarrierWave::Uploader::Base

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

# 登録時にファイル選択をしなかった場合、デフォルト画像をimageとして渡す
  def default_url
      'board_placeholder.png'
　　　　end

# 許可する画像形式を指定
　　　　　def extension_allowlist
    %w[jpg jpeg png gif]
　　　　end
end
```
(デフォルト画像はassets/images配下に設置すること)

### アップロードされた画像ファイルをGithubで管理したくないので、.gitignoreファイルに下記の記載をする
```php:.gitignore
# Ignore uploaded files in development
 /public/uploads
```

### アップローダーをモデル内のカラムに取り付けたいので、まずは画像保存用のカラムをDB内に用意する。
マイグレーションファイルを作成して、マイグレーションを実行。
(カラム名はboard_image。型はString)
```
rails g migration add_board_image_to_boards board_image:string
rails db:migrate
```

### アップローダーをカラムと関連付ける
下記のように書くことで、レコードの保存時に画像が自動的にpublid/uploads配下に保存され、DBのboard_imageカラムには画像のファイル名のみが保存される。
```PHP:app/models/board.rb
class Board < ApplicationRecord
  belongs_to :user
  mount_uploader :board_image, BoardImageUploader #追記

  validates :title, presence: true, length: { maximum: 255 }
  validates :body, presence: true, length: { maximum: 65_535 }
end
```
### boardsコントローラのストロングパラメータにboard_imageカラムを追加する、「board_image_cache」の記述を追記する
```
  def board_params
    params.require(:board).permit(:title, :body, :board_image,　:board_image_cache)　#画像のキャッシュの設定あり
  end
```
### 掲示板投稿フォームのビューファイルに、画像投稿用のフィールド＆「(カラム名)_cache」という名前のhidden_fieldを追加する
```
<div class="form-group">
   <%= f.label :board_image %>
   <%= f.file_field :board_image, class: 'form-control mb-3', accept: 'image/*' %>
   <%= f.hidden_field :board_image_cache %>
 </div>

```
accept: 'image/*は、画像ファイル全般を指定している。例えば、video/*は動画ファイル全般を指す。

### 「board_image」カラムの入力項目表示を日本語で表示したいので、ja.ymlファイルに翻訳を入力する。
（今回は「サムネイル」と表示させたい）
```
board_image: サムネイル
```


### 掲示板の部分テンプレートに、アップロードした画像のURLを指定
```
<div class="col-sm-12 col-lg-4 mb-3">
  <div id="board-id-<%= board.id %>">
    <div class="card">
      <%= image_tag board.board_image_url, class: 'card-img-top', size: '300x200' %> #追記
      <div class="card-body">
        <h4 class="card-title">
          <a href="#">
            <%= board.title %>
          </a>
        </h4>
        <div class='mr10 float-right'>
          <a href="#"><%= icon 'fas', 'trash', class: 'pr-1' %></a>
          <a href="#"><%= icon 'fa', 'pen' %></a>
        </div>
        <ul class="list-inline">
          <li class="list-inline-item">
            <%= icon 'far', 'user' %>
            <%= board.user.decorate.full_name %>
          </li>
          <li class="list-inline-item">
            <%= icon 'far', 'calendar' %>
            <%= l board.created_at, format: :long %>
          </li>
        </ul>
        <p class="card-text"><%= board.body %></p>
      </div>
    </div>
  </div>
</div>
```
↓
以下の部分
```
<%= image_tag board.board_image.url, class:'card-img-top', size:'300x200' %>
```



## 参考にしたサイト
[【Rails】CarrierWaveの使い方をざっくりまとめてみた](https://zenn.dev/yukihaga/articles/e63a224431bc96)
[【Rails】 CarrierWaveチュートリアル](https://pikawaka.com/rails/carrierwave)
[rails 画像アップロード機能の追加](https://note.com/tumu1632/n/nb17b11cf0ba4)