Active Storageで複数枚の画像を添付する
Active storageについては以前まとめたのでこちらを参照してください。
今回は画像を複数枚同時にアップロードできるようにしていきます、またアップロードされている画像を個別に削除できるような実装もしていきます。
複数枚の画像を添付する
has_many_attachedを使用して、画像とモデルの間に1対多の関係を設定します。多数の画像をアタッチできるようになります。
たとえばアプリケーションにMessageモデルがあるとします。メッセージごとに多数の画像を持たせるようにMessageモデルを定義します。
message.rb
has_many_attached :images
ビューとコントローラを書き換えて、imagesを受け取れるように実装していきます。※ビューではsimple_formとslim記法を使っています。
messages/edit.html.slim = f.input :images, as: :file, hint: 'JPEG/PNG (1200x400)', input_html: { multiple: true } - if @message.images.attached? - @message.images.each do |image| = image_tag image.variant(resize: "100x100").processed
{ multiple: true }
は複数枚画像投稿の場合に追記することによって、複数枚画像を選択することができます。
input_html: { multiple: true }
と記載しているのはsimple_formを使用しているのでinput_html:
は必要な記述のようです。
参考 : 【Rails5】carrierwaveとsimple_formで複数の画像をアップロードする | ニートエンジニア
if文でimagesが添付されたらプレビューが表示されるように記述しました。
variant
で画像を使用したいサイズに加工しています。
processed
は保存されたサイズと加工しようとしているサイズが同じなら自身のインスタンスを返すという意味です。
messages_controller.rb class MessagesController < ApplicationController def create message = Message.create!(message_params) redirect_to edit_message_path end private def message_params params.require(:message).permit(:title, :content, images: []) end end
images: []
と書く理由は、複数枚の画像がアップロードされた時に送られてくるパラメーターは配列に格納されているため、以下のようにストロングパラメーターも配列形式にする必要があるからです。
参考 : 【Rails6.0】Active Storageを用いた単数枚、複数枚画像投稿の実装手順をそれぞれ解説|TechTechMedia
これで複数枚の画像の添付ができるようになりましたが、バリデーションをつけていないので想定外の画像のタイプやサイズが送られてくる可能性があります。次で画像のバリデーションについて説明していきます。
Active Storageのバリデーション
Active Storageにはデフォルトのバリデーションがないため、自分でバリデーション設定をしなくてはいけません。
下記のようにcontent_type
で画像のファイルタイプ、maximum
で画像のファイルサイズを設定できるようにしておきます。
message.rb validates :images, attachment: { content_type: %r{\Aimage/(png|jpeg)\Z}, maximum: 524_288_000 }
上記で使用した2つのバリデーションで使用するメソッドを定義していきます。ここで使うのが個別のカスタムバリデーターです。バリデーションのメソッドをattachment_validator.rbに記入していきます。※ここではmaximum
メソッドを用いて説明していきます。
validators/attachment_validator.rb class AttachmentValidator < ActiveModel::EachValidator include ActiveSupport::NumberHelper def validate_each(record, attribute, value) return if value.blank? || !value.attached? has_error = false if options[:maximum] if value.is_a?(ActiveStorage::Attached::Many) value.each do |value| unless validate_maximum(record, attribute, value) has_error = true break # breakは繰り返し終了 end end else has_error = true unless validate_maximum(record, attribute, value) end end end private def validate_maximum(record, attribute, value) if value.byte_size > options[:maximum] record.errors[attribute] << (options[:message] || "は#{number_to_human_size(options[:maximum])}以下にしてください") false else true end end end
value.is_a?(ActiveStorage::Attached::Many)
は継承関係を遡ってどのクラスに属しているか調べています。一枚の画像を受け取るレコードの時にもこのメソッドを使えるように、条件分岐を使って定義しています。
validate_maximum
メソッドではvalue
のbyte_size
がmessageモデルで定義したバイト数を超えている場合はエラーメッセージが出るように定義しています。
参考 : ActiveStorageのバリデーション - Qiita
Active Storageの削除機能
最後にアップロードした画像を削除する方法について説明していきます。私はmessagesコントローラのdestroyアクションに画像削除の記述をしていたのですが、これだとmessageを削除する定義を実装することができなくなってしまいます。またmessagesコントローラに画像削除用に独自のアクション名を増やすこともできますが、RESTに基づく7つのactionのみのほうがわかりやすく管理しやすいです。なので新たにControllerを作成してdestroyアクションを追加する方法が一番良いです。
まず最初に画像を削除するためのコントローラを作成します。admin/commentsディレクトリ配下にattachmentsコントローラを作成します。
$ rails g controller admin::comment::attachments destroy
ここで作成されたビューファイル、ルーティングの記述などの不要なものは削除しておきます。
そしてルーティングはadminとcommentsのresourceにネストさせて定義すると関連が分かりやすくなります。
routes.rb namespace :admin do resource :comments do resources :attachments, only: %i[destroy] end end
しかしこのままだとルーティングエラーが出てしまします。どのようなルーティングになっているか確認すると、下記のようになっていました。
attachmentsコントローラはadmin/commentsディレクトリの配下にあるので、admin/attachments#destroyではおかしいですよね。
namespace :admin do resource :comments do resources :attachments, only: %i[destroy], controller: 'comments/attachments' end end
このようにコントローラを指定してあげるとadmin/commentsディレクトリを指定することができるのでエラーは解決しました。
他にもresourcesにmodule: :comments
と記載する方法もあります。
次に作成したattachmentsコントローラのdestroyアクションに画像の削除をするための定義をしていきます。
class Admin::Comments::AttachmentsController < ApplicationController def destroy image = ActiveStorage::Attachment.find(params[:id]) image.purge redirect_to root_path end end
ActiveStorage::Attachment.find(params[:id])
は全てのActive Storageで管理している画像ファイルのIDとパラメータで送られてきたIDの一致するものを探しています。
image.purge
で探し出した画像を削除しています。purge
はActive Storageで使う削除メソッドです。
最後に削除ボタンを実装していきます。
messages/edit.html.slim = f.input :images, as: :file, hint: 'JPEG/PNG (1200x400)', input_html: { multiple: true } - if @message.images.attached? - @message.images.each do |image| = image_tag image.variant(resize: "100x100").processed = link_to '削除', admin_comments_attachment_path(image.id), method: :delete, class: %w[btn btn-danger]
admin_comments_attachment_path(image.id)
でパスを送るときに画像IDを指定しています。