CarrierWaveでつまづいた箇所(multiple・Cache・_mounter)についてまとめる


# はじめに

一切かかわりのない方々が書いたコードを保守する機会がありました。
そこでファイルの保存に**CarrierWave**を使っており、あまりみかけない使い方をしていて理解に時間がかかったので断片的にはなりますがまとめていきます。

# つまづいたところ

CarrierWaveは利用されている割にはネットに情報がないイメージでした
以下が理解に時間がかかった箇所です

## ファイルを複数あげたい

こちらの記事を参考にしました。
[【Rails】Carrierwaveを用いて画像を複数アップロードする方法](https://qiita.com/mattan5271/items/a0ff8ab9a830d2cfcc7e)

ファイルを単体で上げられる前提で以下を修正しました。

複数ファイルを投稿できるように修正
```erb:new.html.erb
= f.file_field :images, multiple: true
```

CarrierWaveで複数の写真を保存できるように修正
```book.rb
mount_uploaders :images, ImageUploader
```
ポイントは、`uploaders`にすること

POSTしたパラメータを配列として受けとれるように修正
```book_controller.rb
def book_params
  params.require(:book).permit(:title, :body, { images: [] })
end
```

## キャッシュを効かせたい

ファイルアップロードをしたときに、別の箇所(例えばタイトルを入力しないなど)でバリデーションが発生したときに、あげたファイルのデータが消えてしまうため、上げなおさないといけないという問題が起きます。そこでキャッシュを効かせる必要があり対応しました。

[【個人メモ】carrierwaveでcacheを取り回す](https://qiita.com/junara/items/190fa7413237abdbf9c2)

まず、`attr_accesor`をモデルに追加します。

```book.rb
class Book
  attr_accessor :images_cache
  mount_uploaders :image, ImageUploader
end
```

`attr_accesor`については[Railsから入った人へ【attr_accessor】って？](https://qiita.com/Hassan/items/0e034a1d42b2335936e6)を見るとわかります。DBを使わないでカラムを定義することができるようになります。

つぎに、View側に`hidden_field`を追加します。

```erb:new.html.erb
<%= form_with @user, url: user_confirm_path, local: true do |f| %>
  (省略)
  <%= f.file_field %>
  <%= f.hidden_field :images_cache %>
<%= f.submit %>
```

バリデーションで失敗したときにアップロードしたファイルをを`@book.images_cache`に追加してから、newをレンダリングすることで、次のPOSTのときに`params[:images_cache]`から前にアップロードしたファイルのデータを取得することができます。

`image_cache`への追加はCreateの最後で行います。

```book_controller.rb
  def create
    @book = current_user.users.new(book_params)
　　if @book.save!
      redirecto_to test_path
    else
      @book.images_cache = @book.images.map{ |image| images.cache_name }
      render :new
    end
  end
```

このように保存に失敗したらキャッシュを保存するように設定します。
複数ファイルをアップロードしているので、imagesをmapで回して、`cache_name`でキャッシュ用のIDを取得して配列にして`image_cache`に保存しています。

## _mounterについて

私が扱ったコードには`_mounter`を利用したメソッドがありました。

```book_form.rb

def set_image
  _mounter(:images).cache
end

def get_image
  _mounter(:images).uploaders
end
```

いきなりでてきて戸惑いましたが、以下のコードを参考にすることで理解ができました。

https://github.com/carrierwaveuploader/carrierwave/blob/master/lib/carrierwave/mounter.rb

_mounterを使うと以下のファイルのメソッドが呼び出されます。
`_mounter(:images).cache`で実際にCarrierWaveでファイルを保存して、`_mounter(:images).uploaders`で保存したファイルに関するUploadersオブジェクトを取得することができます。このオブジェクトを用いてCreateなどをするとCarrierWaveのレコードが作成されます。

## キャッシュの復元

複数ファイルのキャッシュの復元に以下を行っていました。

```book_form.rb
def get_cache_images
  _mounter(:images).cache_names=images_cache
end
```

`images_cache`はhiddenで保持したキャッシュです。
ここでも_mounterを利用して、cache_nameメソッドを呼び出してUploaderオブジェクトを取得しています。
以下は_mounterの該当するコードです。

```_mounter.rb
    def cache_names=(cache_names)
      cache_names = cache_names.reject(&:blank?)
      return if cache_names.blank?
      clear_unstaged
      cache_names.each do |cache_name|
        begin
          uploader = blank_uploader
          uploader.retrieve_from_cache!(cache_name)
          @uploaders << uploader
        rescue CarrierWave::InvalidParameter
          # ignore
        end
      end
    end
```

`_mounter(:images).cache_names=images_cache`とすることでgetterを呼び出します。左辺に右辺を入れているように見えるので注意が必要です。メソッドを呼び出しているだけです。
これでキャッシュの復元ができるようになります。復元は`uploader.retrieve_from_cache!(cache_name)`の箇所で実際には行っています。

## 名前が取れない

```
images.map{ |i| i[:name]}
```

で取れました。ハッシュでとるのと複数の考慮が必要でした

# おわりに
コードに関しては適当に書いているものなので動くことは保証できませんのでご理解ください。
今回はCarrierWaveについての考え方や使い方をまとめました。バックエンドを気にせずに簡単利用することができるのがよいところですが、カスタマイズもうまくできることがわかりました。

# 参考文献
- [【Rails】Carrierwaveを用いて画像を複数アップロードする方法](https://qiita.com/mattan5271/items/a0ff8ab9a830d2cfcc7e)
- [【個人メモ】carrierwaveでcacheを取り回す](https://qiita.com/junara/items/190fa7413237abdbf9c2)

https://github.com/carrierwaveuploader/carrierwave/blob/master/lib/carrierwave/mounter.rb