2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2020-03-21

はじめに

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

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

開発環境・前提

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メソッドが使われてしまう。

つまり、1つの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有り
<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無し
<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についての知識がかなり深まった。欲張りな性格なので、いろんな記事のいいとこ取りをしようとした結果、こんなにもの何重もの罠を自分で仕掛けて自分でハマるということになってしまった。次からは是非とも一つ一つの用法や性質を理解した上で、実装していきたい。

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

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

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?