プログラミング学習 備忘録

Railsを学習していく上での技術メモ。学んだことや解決したエラーなどを記録していきます。

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