Active Storageで複数画像をアップロード

以前、Active Storageで画像をアップロードという記事を書いたのですが、
今回は複数の画像をアップロードしてみます。

準備

rails new images_app
cd images_app
rails g scaffold User name:string

rails newしてscaffoldします。

rails active_storage:install

active_storage_blobsactive_storage_attachmentsの2つのテーブルが作成されるマイグレーションファイルが生成されるので

rails db:migrate

migrateします。
slimとsimple_formを使うのでGemfileにslim-railshtml2slimsimple_formを記述し、mini_magickも必要なのでコメントアウトを外します。

bundle install

して

bundle exec erb2slim app/views -d

でerbファイルをslimファイルに変換します。
これで準備OKです。

モデル

Userが画像を複数持てるようにしたいので

# user.rb

class User < ApplicationRecord
  has_many_attached :images
end

has_many_attachedで関係を設定します。
imagesの部分は任意の複数形の単語を名付けます。

コントローラ

imagesを受け取れるようにストロングパラメータを設定します。

def user_params
  params.require(:user).permit(:name, images: [])
end

permitにimages: []を追加しました。
複数の画像が送信された時、[]の中に配列の形で入ります。

Image from Gyazo

ちょっと分かりづらいですが、
"images"=>[#<ActionDispatch::Http::UploadedFile:0x00007fcb8b360e08…
のところです。

次は複数アップロードされた画像を個別に削除できるようにしたいので、そのためのコントローラを作成します。
Userが持つファイルの制御なので、user/attachments_controller.rbとし、画像削除のためのdestroyアクションを作成します。

# user/attachments_controller.rb

class User::AttachmentsController < ApplicationController
  def destroy
    image = ActiveStorage::Attachment.find(params[:id])
    image.purge
    @user = User.find(params[:user_id])
    redirect_to user_path(@user)
  end
end

ActiveStorage::Attachment.find(params[:id])で画像を取ってきて、
purgeで削除です。
purgeはActiveStorageが用意しているメソッドで、添付ファイルを削除してくれます。

ルーティング

# routes.rb

Rails.application.routes.draw do
  root 'users#index'
  resources :users do
    resources :attachments, controller: 'user/attachments', only: %i[destroy]
  end
end

usersが持つattachments、という意味でネストさせています。

ビュー

# index.html.slim

p#notice
  = notice
h1
  | Users
table
  thead
    tr
      th
        | Name
      th[colspan="3"]
  tbody
    - @users.each do |user|
      tr
        td
          = user.name
        td
          - if user.images.attached?
          - user.images.each do |image|
            = image_tag image.variant(resize:'100x100').processed
        td
          = link_to 'Show', user
        td
          = link_to 'Edit', edit_user_path(user)
        td
          = link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' }
br
= link_to 'New User', new_user_path

Image from Gyazo

# show.html.slim

p#notice
  = notice
p
  strong
    | Name:
  = @user.name
p
  strong
    | Images:
  - if @user.images.attached?
  - @user.images.each do |image|
    = image_tag image.variant(resize:'100x100').processed
    = link_to 'Destroy', user_attachment_path(@user.id, image.id), method: :delete
= link_to 'Edit', edit_user_path(@user)
|  | 
= link_to 'Back', users_path

Image from Gyazo

# _form.html.slim

= simple_form_for user do |f|
  = f.error_notification
  = f.input :name
  = f.input :images, as: :file, input_html: { multiple: true }

  - if @user.images.attached?
  - @user.images.each do |image|
    = image_tag image.variant(resize:'100x100').processed
    = link_to 'Destroy', user_attachment_path(@user.id, image.id), method: :delete
  br
  = f.button :submit, 'Submit'

Image from Gyazo

複数画像のアップロード、個別の削除ができました。

最後に

attachments_controllerの中で@userを定義してないのにredirect_toのpathの引数に@userを入れたり、
画像の個別削除のlink_toのpathの引数を間違ってたり、
初歩的なところでつまづいて時間が溶けてしまいました。
解決した後に冷静になってみればわかるんですけどね…
では今回は以上です。ありがとうございました。

参考サイト

ActiveStorageについて|moeno|note

Active Storage の概要 - Railsガイド

ActiveStorageを使って複数画像管理をしてみる - Qiita