ActiveStorage 画像削除機能とカスタムバリデーションについて
今回はRailsのデフォルト機能であるActiveStorageを使って画像を扱う課題だったのですが、非常に難しく途中で断念してしまったので、解答例のコードを読み解いて行こうと思います。 Swiperを使った画像のスライド機能に関してはそこまでややこしくないので今回は省きます。
ActiveStorageを使った画像削除機能
resource :site, only: %i[edit update] do resources :attachment, controller: 'site/attachments', only: %i[destroy] end
ルーティングは上記の通り。 resourcesをネストさせて、controllerを明示的に渡している。 これによりルーティングは下のようになり、:idを渡すことができるようになる。
admin_site_attachment DELETE /admin/site/attachment/:id(.:format) admin/site/attachments#destroy
Controllerは下記のようにコーディング。
class Admin::Site::AttachmentsController < ApplicationController def destroy authorize(current_site) # Active::Storage::Attachmentで紐づけられているデータを全て取得できる。 image = ActiveStorage::Attachment.all.find(params[:id]) image.purge redirect_to edit_admin_site_path end end
ビューファイルはこのように。
- if @site.favicon.attached? = image_tag @site.favicon_url('32x32') = link_to admin_site_attachment_path(@site.favicon.id), method: :delete i.fa.fa-trash | 削除 br br = f.input :og_image, as: :file, hint: 'JPEG/PNG (1200x630)' - if @site.og_image.attached? = image_tag @site.og_image_url(:ogp), class: 'img-responsive' = link_to admin_site_attachment_path(@site.og_image.id), method: :delete i.fa.fa-trash | 削除 br br = f.input :main_images, as: :file, hint: 'JPEG/PNG (1200x630)', input_html: { multiple: true } # multiple: true をつけることによって、複数画像を一気にアップロードすることが可能 - if @site.main_images.attached? - @site.main_images.each do |image| = image_tag image.variant(resize: "200x105"), class: 'main_images' # active storageでは、variantメソッドによってファイルのサイズを指定できる = link_to admin_site_attachment_path(image.id), method: :delete i.fa.fa-trash | 削除
faviconもog_imageもmain_imagesも、保存された画像は全てActiveStorage::Attachmentに格納されるため、それぞれ一意性を持っているのでIDが重複されることはない。 なのでadmin/site/attachmentコントローラのdestroyアクションの使い回しが可能である。
カスタムバリデーションについて
こちらはコード自体が少しややこしく、理解するのにだいぶ時間がかかってしまった。
参考にしたURL: 【Rails】カスタムバリデータの使い方 - TASK NOTES
class AttachmentValidator < ActiveModel::EachValidator include ActiveSupport::NumberHelper def validate_each(record, attribute, value) return if value.blank? || !value.attached? has_error = false # valueが空の場合はエラーを返さずにそこで処理を終わらせる if options[:maximum] # Modelのバリデーションでmaximumオプション指定されているときは下の処理を行う if value.is_a?(ActiveStorage::Attached::Many) # is_a?メソッドは、オブジェクトの中身(value)が引数で渡されたもの(ActiveStorage::Attached::Many)であればtrueを、それ以外ならばfalseを返す。 value.each do |one_value| # 今回はvalueが複数画像の場合はone_valueごとにバリデーションをかけている unless validate_maximum(record, attribute, one_value) has_error = true # validate_maximumがfalseの場合はhas_error = trueを返す end end else has_error = true unless validate_maximum(record, attribute, value) #こちらはvalueが複数画像ではなかった場合の処理 end if options[:content_type] if value.is_a?(ActiveStorage::Attached::Many) value.each do |one_value| unless validate_content_type(record, attribute, one_value) has_error = true end end else has_error = true unless validate_content_type(record, attribute, value) end record.send(attribute).purge if options[:purge] && has_error # バリデーションでpurge: trueが指定されており、尚且つ上の分岐でhas_error = trueとなっている時に送られてきた画像データをpurgeする。 end private def validate_maximum(record, attribute, value) if value.byte_size > options[:maximum] # options[:maximum]で、Modelのバリデーションで定義されたmaximumのハッシュの値を取得できる。 record.errors[attribute] << (options[:message] || "は#{number_to_human_size(options[:maximum])}以下にしてください") false else true end end def validate_content_type(record, attribute, value) if value.content_type.match?(options[:content_type]) # こちらも同じくoption[:content_type]で、Modelのバリデーションで定義されたcontent_typeのハッシュの値(つまり指定されたファイル形式)をとってこれる。そしてmatch?メソッドでvalueのファイル形式と一致しているかチェック。 true else record.errors[attribute] << (options[:message] || 'は対応できないファイル形式です') false end end end