【Rails】画像の複数投稿機能の実装方法 carrierwave/paperclip


下記URLを参考に実装しました。
http://arthurxxx.hatenablog.com/entry/2018/02/05/234638
https://github.com/carrierwaveuploader/carrierwave#multiple-file-uploads

##前提
・railsのバージョンは6.2.0。
・gemはcarrierwaveとpaperclipを使用。
・上記のgemを事前にインストールしておいてください。
・gymモデルとpictureモデルが存在する。
・pictureモデルはgymモデルに依存する。（gymモデルが親、pictureモデルが子）
・accepts_nested_attributes_for メソッドは使いません。



##手順

###carrierwaveのuploderモデル（カラムはpicture）を準備し、gymモデルにpictureカラムを追加する。

```
$   rails generate uploader モデル名  picture:string 

$   rails generate migration AddPictureToGyms

$   rake db:migrate
```





###uploders/picture_uploderに以下を記述。
```
class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [200, 200]
  

    if Rails.env.production?
    storage :fog
  else
    storage :file
  end

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end
```

<br>

###models/gym.rbに以下を追記。


```models/gym.rb
has_many :pictures, dependent: :destroy    #gymモデルはpictureを複数持つ
mount_uploader :picture, PictureUploader   #pictureカラムにアップした画像をPictureUploderが処理する
serialize :picture, JSON
validates :picture, presence: true         #pictureカラムはデータが入ってる必要がある
```

<br>

###model/picture.rbに以下を追記。

```model/picture.rb
mount_uploaders :picture, PictureUploader   #pictureカラムにアップした画像をPictureUploderが処理する。mount_uploadersとsをつける。
belongs_to :gym, dependent: :destroy        #pictureモデルはgymモデルに属する
```

<br>

###views/shared/_post_form.html.erbに以下を追記。

```views/shared/_post_form.html.erb
<%= form_with(model: @gym,  local: true) do |f| %>

  <%= f.label :ジム名 %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :口コミ %>
  <%= f.text_field :content, class: 'form-control' %>

  <%= f.label :写真 %>
  <% gym.pictures.each do |picture| %>
  <%= image_tag(picture.url) %>
<% end %>
  <%= f.file_field :picture, type: :file, multiple: true %>      #paperclipの機能で画像を複数投稿するための記述

  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
```
<br>

###インスタンス変数@gymとpicutreを紐づけるためにcreateメソッドの部分に@gym.picture = params[:picture]を記述。
###pictureカラムがデータベースに保存できるようにgym/controller.rbのgym_paramsメソッドに{picture:[]}を追記。


```gym/controller.rb
class GymsController < ApplicationController
  before_action :logged_in_user, only: [:show, :create, :destroy]
  before_action :correct_user,   only: :destroy

  def index
    @gym  = current_user.gyms.build
       #debugger
    @gyms = Gym.paginate(page: params[:page], per_page: 9)
  end

  def new
    @gym = gym.new
  end

  def show
    @gym = Gym.find(params[:id])
    @comment = Comment.new
    @comments = @gym.comments.paginate(page: params[:page], per_page: 10)

    @blogs = Blog.all
  end

  #ログインしたユーザーがジムを投稿する
  def create
    @gym = Gyms.build(gym_params)
    @gym.picture = params[:picture]
    if @gym.save
      flash[:success] = "投稿ありがとうございます!"
      redirect_to request.referrer ||  'gyms_path'
    else
      render 'gyms/index'
    end
  end

  #投稿を削除
  def destroy
    @gym.destroy
    flash[:success] = "ジムを削除しました"
    redirect_to request.referrer ||  'gyms_path'
  end

  private

  def gym_params
    params.require(:gym).permit(
      :name,
      :content,
      {picture:[]}
    )
  end

  def correct_user
    @gym = current_user.gyms.find_by(id: params[:id])
    redirect_to gyms_path if @gym.nil?
  end
end
```

<br>

###複数投稿された画像を表示するviews/gyms/show.html.erbに以下を記述。

```views/gyms/show.html.erb
<% if @gym.picture? %>
  <% if @gym.picture.each do |picture| %>
  <%= image_tag(picture.url) %>
  <% end %>
<% end %>
```

あとは画像を複数選択すればOKです。

<br>
##つまづいた点
####views/shared/_post_form.html.erbで入力した値がviews/gyms/show.html.erbで表示されない。

####【原因】
views/shared/_post_form.html.erbで格納している変数と、views/gyms/show.html.erbで持ってくるはずの変数名が異なっていた。

以下、画像が表示されない間違っているコードです。

```views/shared/_post_form.html.erb
<%= form_with(model: @gym,  local: true) do |f| %>

  <%= f.label :ジム名 %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :口コミ %>
  <%= f.text_field :content, class: 'form-control' %>

  <%= f.label :写真 %>
  <% gym.pictures.each do |picture| %>
  <%= image_tag(picture.url) %>
<% end %>
  <%= f.file_field :picture, type: :file, multiple: true %>

  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>


```

```views/gyms/show.html.erb

<% if gym.picture? %>   　　　　　　　　　　　→ここが@gym.pictureである必要がある。
  <% if gym.picture.each do |picture| %>  →ここが@gym.picture~である必要がある。
  <%= image_tag(picture.url) %>
  <% end %>
<% end %>
```

<br>
views/shared/_post_form.html.erbでは、**インスタンス変数@gymに、pictureを、gym.pictureとして保存しています。**
（form_withメソッドで定義されているインスタンス変数名は@gymになっています。）
<br>

ですので、

<br>

**indxアクションやshowアクションで表示をする際にはviews/shared/post_form.html.erbの中の、formwithメソッドで使った@gymを使用する必要があります。**



モデル名を変えて自身のコードに置き換えてみてください。