複数画像投稿で盛大に自爆した時の確認事項

##はじめに
某フリマアプリの模倣アプリを開発中、出品機能実装で複数画像の登録に死ぬほど手を焼いたので、備忘録として掲載します。


誤った記述などあればご指摘いただけると幸いです。

##開発環境・前提

Ruby 2.5.1p57
Ruby on rails 5.2.3
jquery-rails 4.3.5
haml-rails 2.0.1
sass-rails 5.1.0
CarrierWave 2.1.0 

##完成コード

先に完成コードを載せておく。

```image.rb
  belongs_to :item, optional: true
  validates_presence_of :item
  validates :content, presence: true
  mount_uploader :content, ImageUploader
```


```item.rb
  belongs_to :brand, optional: true
  belongs_to :user, optional: true
  belongs_to :category,  optional: true
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
```

```items_controller.rb
def new
    @item = Item.new
    @brands = Brand.all
    @category_parent_array = ["指定なし"]
    Category.where(ancestry: nil).each do |parent|
      @category_parent_array << parent.name
    end
    @item.images.build
  end

  def create
    @item = Item.new(item_params)
    
    if @item.save!
      @image = @item.images.create
      redirect_to :root
    else
      render :new
    end
  end

private
  def item_params
    params.require(:item).permit(
      :name, :description, :condition, :price, 
      :fee, :brand_id, :area, :shipping_days, 
      images_attributes: [:content, :id, :_destroy]
      ).merge(user_id: current_user.id, category_id: params[:category_id], brand_id: params[:item][:brand_id])
  end
```



```new.html.haml
#画像投稿フォームの記述部分
.main-items
    = form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content"
              .js-remove
                %span.js-remove__text
                  削除
```


##モデルへのmout_uploaderの記述

before

```image.rb
mount_uploaders :content, ImageUploader

```

after

```image.rb
mount_uploader :content, ImageUploader

```

mount_uploaderとするかmount_uploadersか。


```error.message
NoMethodError (undefined method `map' for #<ActionDispatch::Http::UploadedFile......> #省略
Did you mean?  tap):

app/controllers/items_controller.rb:68:in `create'
```
mount_uploadersにすると、デフォルトでmapメソッドが使われてしまう。

つまり、１つのfile_fieldに複数の画像データが入っている配列である必要があるのだ。

そういう時は、file_fieldにmultiple: trueを記載する必要がある。

##multiple: true
multiple: true を記述すると、一つのfile_fieldに複数画像をアップロードしようとする。

修正前の記述

```new.html.haml
= form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除
```


inputタグのtype[file]部分のHTML(検証)

######multiple有り

``` html
<input multiple="multiple" class="image-wrapper__image-box__js__label__file js-file" 
id="item_images_attributes_0_content" type="file" 
name="item[images_attributes][0][content][]">

```

######multiple無し

```html
<input class="image-wrapper__image-box__js__label__file js-file"
 id="item_images_attributes_0_content" type="file"
 name="item[images_attributes][0][content]">
```

デフォルトで設定されるname属性が変わる

私の場合は、各file_fieldに一つずつ保存させるようなコードを書いていたのにもかかわらず、multipleの記述をしてしまっていて、エラーが起きた。



##labelタグのfor属性
labelタグのfor属性は、連動させたい子要素のidの値を記述する必要がある。

###### 修正前のlabel部分の記述

```html.haml

            %label.image-wrapper__image-box__js__label
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除
```

###### 修正後

```html.haml

            = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除
```


labelタグの性質をよく理解せずに使っていた。
修正前の記述だと、検証でみてみるとわかるが、labelタグにfor属性が付与されておらず、inputタグのid属性(ここでは、item_images_attributes_0_content)に対応しておらず、不具合がおきた。


後からわかったことだが、、hidden_fieldの記述を消してやれば、%labelのままでも支障はないことがわかった。



##hidden_fieldの記述
修正前の記述

```new.html.haml
= form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required"
                = i.hidden_field :item_id, value: @item.id
              .js-remove
                %span.js-remove__text
                  削除
```
imagesのitem_idをparamsに送るための記述をしていたが、idや外部キーはデフォルトで送られるようになっているため、必要なかった。逆に、これがあることによって、:contentが入っていない空のfile_fieldがhidden_fieldと共にparamsに送られてしまうため、validationに引っかかってしまう。



## 最後に
チーム開発で商品出品機能を担当したことにより、HTML&CSS, jQuery, Rubyについての知識がかなり深まった。欲張りな性格なので、いろんな記事のいいとこ取りをしようとした結果、こんなにもの何重もの罠を自分で仕掛けて自分でハマるということになってしまった。次からは是非とも一つ一つの用法や性質を理解した上で、実装していきたい。

でも、何かしらの初学者ってこういう風に泥臭く成長していくのかなぁとも思った。

諦めたら、そこで試合終了だよ。