[Rails]refile + fields_for の書き方

## やりたいこと
・refileを使用して画像アップロードの実装
・複数モデルに一度で保存したいため、fields_forを使用

この２つの組み合わせの書き方が調べてもほとんど出てこなかったので書いておく。

## 環境
Ruby 2.5.1
Rails 5.2.3

## 導入、基本的な使い方参考
[refileの基本と複数画像のアップロード](https://qiita.com/nekosuke_/items/501e7ba1b286c51c1be9)
導入方法や基本的な使い方は上記サイトを参考にしてください。

ただ一点、Gemfileのインストールは上記サイトの通りだとエラーが出たので、
下記コードで実行できました。

```ruby:Gemfile
gem "refile", require: "refile/rails", github: 'manfe/refile'
gem "refile-mini_magick", github: 'refile/refile-mini_magick'
```
参考サイト様だと`github: 'refile/refile'`となってる部分、
bundle install時にエラーが出たので、エラーメッセージに従って`github: 'manfe/refile'`としたら成功しました。

# fields_forを使用する場合の書き方
前提:
・親モデルと子モデルは１対１のアソシエーション
・１画像のみアップロードするものとする

## 入力フォーム
```ruby:ビューファイル.html.haml
= form_with model: @user do |f|    # 親モデルをuserと仮定
   # 省略

  = f.fields_for :image do |i|     # 子モデルをimageと仮定
    = i.attachment_field :image    # 「:image」 はimage_idカラムの_idを抜いたもの
```
`attachment_field`を使用する。
これで`file_field`と同じファイル選択フォームが作成される。

## モデルのアソシエーション
```ruby:モデル.rb
  # userモデル
  has_one :image, dependent: :destroy    # 「dependent: :destroy」でuserを削除すると関連するimageも削除される
  accepts_nested_attributes_for :image   # fields_forに必要な記述
  accepts_attachments_for :image, attachment: :image  # refileに必要な記述

  # imageモデル
  attachment :image # 「:image」 はimage_idカラムのidを抜いたもの
  belongs_to :user
```
`attachment :image`の`:image` はモデル名ではなく、**カラム名**なので間違えないように。
カラムは「image_id」で登録していても、ここでは「_id」は抜かして書く。

## コントローラーの記述
```ruby:users_controller.rb
  def new
    @user = User.new
    @user.build_image  # fields_forのために親子モデルを関連付け
  end

  def create
    User.create(user_params)
  end

  def index
    @users = User.all.includes(:image)  # この呼び出し方が必須というわけではないです
  end

  def user_params
    params.require(:user).permit(
      :name,
      :email,
      image_attributes: [
      :id,
      :image,           # 「image_id」カラムでも「_id」は入れなくて良い
      :image_cache_id   # これを記述しておくとキャッシュファイルも保存される（らしい）
      ]
    )
  end
```
最低限の記述のみ書いてます。

ちなみに、`.includes(:image)`で子モデルが２つ以上ある場合は、
`.includes([:image, :address])`
のように、引数内で`[]`で囲む必要があるので注意。


## ビューファイルでの保存した画像の表示
```ruby:ビューファイル.html.haml
= attachment_image_tag @user.image, :image, :fill, 200, 120, format: "jpg"

# 「@user.image」 は子モデル名。今回はfields_forでuserが親モデルとして紐づいているためこのように指定
# 「:image」 はimage_idカラムのidを抜いたもの
# 「200, 120,」 は表示する画像のサイズ。 「横, 縦,」
```
`attachment_image_tag`を使用して保存したデータを呼び出す。

今回、１画像のみのアップロード前提で書いたが、複数画像アップロードだとかなり変わってくる。

# 複数画像アップロードの場合

## 入力フォーム
```ruby:ビューファイル.html.haml
= form_with model: @user do |f| 
   # 省略

  = f.fields_for :images do |i|     # :imagesと複数形にする
    = i.attachment_field :image, multiple: true, name: "images_attributes[0][image][]"    
# 「multiple: true」 で複数ファイル選択を可能にする
# 「name: "images_attributes[0][image][]"」 でname属性をparamsに合わせる
```
`= f.fields_for :images do |i|`のモデル指定を`:images`と複数形にする。
その下の`= i.attachment_field :image`はimage_idカラムのidを抜いたものなので単数形のままでOK。
`multiple: true`を指定することにより、fields_forもあるためparamsの形がかなり変わり、そのままではparamsに値が入らない。
値がちゃんとparamsに入るよう、`name: "images_attributes[0][image][]"`によりname属性を変わった後のparamsと合わせた形に指定し、値がparamsに入るようにする。



## モデルのアソシエーション
```ruby:モデル.rb
  # userモデル
  has_many :images, dependent: :destroy    # has_manyに変更、複数形に変更
  accepts_nested_attributes_for :images   # 複数形に変更
  accepts_attachments_for :images, attachment: :images  # 複数形に変更

  # imageモデル
  attachment :image # カラムなので単数形のままでOK
  belongs_to :user
```
`attachment :image`も複数形に変えないよう注意

## コントローラーの記述
```ruby:users_controller.rb
  def new
    @user = User.new
    @user.images.build  # 複数形になり記述が少し変更
  end

  def create
    # 先にuserインスタンスにparamsを入れる
    User.new(user_params)
    # 複数画像は配列で持っているため、１つ１つインスタンスに入れる
    params[:images_attributes][:"0"][:image].each do |image|
      @user.images.build(image)
    end
    User.save
  end

  def index
    @users = User.all.includes(:images)  # 複数形に変更。この呼び出し方が必須というわけではないです
  end

  # image_attributes:をストロングパラメーターから削除
  def user_params
    params.require(:user).permit(
      :name,
      :email
    )
  end
```
コントローラーのcreateアクションとストロングパラメーターについては、もっと良い書き方があるはず。
正直、`params[:images_attributes][:"0"][:image]`のストロングパラメーターでの取得の方法がわからなかったため、苦肉の策でストロングパラメーターからimageを消し、createアクションで直接paramsを渡している。

いろいろな書き方を試したが、ストロングパラメーターでは取得できなかった。
できる方法があれば教えていただけると助かります。


## ビューファイルでの保存した画像の表示
```ruby:ビューファイル.html.haml
# 複数画像は配列で保存されているため、１つ１つ取り出して表示
@user.images.each do |image|  # 「@user.images」と複数形に変更
  = attachment_image_tag image, :image, :fill, 200, 120, format: "jpg"

# 「attachment_image_tag image」 の「image」はeach文で指定した変数

```
複数画像の場合については以上。
これで複数画像のDB保存と表示ができるはず。


# 複数保存の問題点
上記のコードで一応複数画像についても、DB保存、表示が可能になった。
しかし問題がある。

元々`fields_for`と`.attachment_field`は１つだけ書いてファイル選択フォームは１つしか無かったものが、例えば３画像保存し、編集しようとするとあら不思議、**フォームが３つに増えています！**

[編集機能について現状わかっていること](https://gist.github.com/yu-smc/0a5713c3927a5108a07c795c32400a41)
>| fields_forは紐づくcaptured_images全てに対して展開されるため、 |
>| editアクションのviewにてfields_forが呼ばれるたびに、 |
>| 選択された画像それぞれに対していちいち展開されてしまう点でした。 |

上記のリンク先でも記述されているが、複数保存したものをeditアクションなどで編集しようとすると、**保存した画像の数だけファイル選択フォームが増える**という現象が起きる。
上記リンク先によるとどうも`fields_for`の仕様で、紐づくレコードが複数あるとその分だけフォームが呼び出されるらしい。

editアクションで表示するビューをnewアクションのビューと同じにしていると確実にフォームが増えてしまうので、編集の仕方をどうにか工夫しなければならない。
何か良い方法があったら教えていただけると助かります。


# 参考

[refile公式](https://github.com/refile/refile)
[Refile のバックエンドに S3 を利用する](https://corp.mansion-market.com/staff/986/)