Techioz Blog

Active Storage は検証が失敗した後でも接続を検出します

概要

これはかなり長い間続いている問題であり、今日に至るまで、まだ解決策が見つかりません。同様の問題をいくつか見たことがありますが、私が経験しているものとはまったく異なります。

私は Rails 5 で登場して以来、Rails Active Storage を使用してきましたが、この特定の問題のため、本番環境で実際に使用することはできませんでした。 Rails 6 の登場時に解決された主な問題は、添付ファイルの検証を実装した場合、レコードは保存されないが、添付ファイル (BLOB) は引き続き保存され、検証がコンテンツ タイプ (たとえば、JPEG 画像であることを確認してから)、添付ファイルが無効になってしまいます(たとえば、テキスト ファイルをアップロードした場合)。たとえば、この添付ファイルを image_tag で「表示」しようとすると、問題が発生します。 Rails 6 では、レコードが実際にデータベースに保存されるときにのみ添付ファイルを保存することで、この問題を解決しています。これで問題の半分しか解決しません。

これは私がまだ経験していて、まだ解決策が見つかっていないことです。

非常に基本的なセットアップがあるとします。名前、メールアドレス、アバターが添付された人物モデル

Class Person < ApplicationRecord
  has_one_attached :avatar
  #Check that image type is jpg or png
  validate :check_image_type

  #Remove avatar flag needed for form
  attr_accessor :remove_avatar

  #purge picture if remove picture flag was ticked on form
  after_save :purge_avatar, if: :purge_requested?

  #Returns a thumbnail version of the property picture
  def thumbnail
    return self.avatar.variant(resize:'100x100').processed 
  end

  private 
    #Validates the image type being uploaded
    def check_image_type
      if avatar.attached? && !avatar.content_type.in?(%("image/jpeg image/png"))
        errors.add(:avatar, "Invalid Avatar Format")
      end
    end

    #Was a purge of the picture requested 
    def purge_requested?
      remove_avatar == "1"
    end

    def purge_avatar
      avatar.purge_later
    end
end 

上記のコードでわかるように、人物にはアバターが 1 つ添付されています。保存時に、画像タイプが jpep または png であることを確認し、そうでない場合は、レコードの保存を妨げるエラーをレコードに追加するだけです。

これはコントローラーのコードです (インデックス、編集、更新、および破棄のアクションは省略しました)。

class PeopleController < ApplicationController
  before_action :set_person, only: [:show, :edit, :update, :destroy]

  def new
    @person = Person.new
  end

  def create
    @person = Person.new(person_params)

    respond_to do |format|
      if @person.save
        format.html { redirect_to @person, notice: 'Person was successfully created.' }
        format.json { render :show, status: :created, location: @person }
      else
        format.html { render :new }
        format.json { render json: @person.errors, status: :unprocessable_entity }
      end
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_person
      @person = Person.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def person_params
      params.require(:person).permit(:name, :email, :avatar, :remove_avatar)
    end
end

次に、フォーム上にアバターが存在する場合はフォーム内に表示し、ユーザーが情報を作成/編集し、必要に応じて別のアバターを選択できるようにします。

<%= form_with(model: person, local: true) do |form| %>
  <% if person.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(person.errors.count, "error") %> prohibited this person from being saved:
      </h2>

      <ul>
        <% person.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <!-- Display avatar if one attach -->
  <%if person.avatar.attached?%>
    <%=image_tag(person.avatar)%>
    Remove <%=form.check_box :remove_avatar%>
  <%end%>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email %>
  </div>

  <!-- Select Picture -->
  <div class = "field">
    <%=form.label :avatar %>
    <%= form.file_field :avatar, accept: 'image/*'%>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

期待される動作は次のとおりです

間違ったファイルの種類を選択すると、レコードは保存されなくなりますが、それは問題ありませんが、非永続レコードの添付ファイルが「表示」されます。そのため、作成アクションでフォームが最初に表示されたときにはアバターは表示されませんでしたが、検証が失敗した後にフォームが再レンダリングされると「空の」アバターが表示されます。これは付属品のせいでしょうか?レコードに添付ファイルがないにもかかわらず (拒否されたため)、メソッドは true を返します。

空白のアバターはちょっとファンキーに見え、リンクが切れた写真のように見えます。ただし、Person クラスのサムネイル メソッドなど、アバター自体に何らかの操作を行うと、次のエラーが生成されます: ActiveStorage::InvariableError

これは付属品のせいでしょうか?属性が true で、アバター属性が有効ですが、関連付けられた BLOB (画像) がありません。そのため、サムネイルにサイズ変更しようとすると、エラーが発生して失敗します。

アバターやアタッチを「クリア」またはリセットする方法を見つけようとしていますか?検証によりレコードが保存できない場合の属性。内部的には、Rails は本来の動作を実行します (ファイルを保存せず、存在する場合は現在のファイルを保持します)。

しかし、フォームに表示される内容 (アバターを表示する場合) は、特に新しいアバターを選択する前にユーザーがアバターを持っていた場合には、ユーザーを混乱させることになります。無効なアバターを選択し、フォームがそれを拒否した場合、再レンダリングでは最初のアバターは表示されず、画像のリンク切れアイコンに置き換えられます。これにより、実際はそうではないのに、以前のアバターが消去されたとユーザーが混乱する可能性があります。現時点では、アクティブ ストレージの内部に深く入り込まずにこれを修正する方法がわかりません (今はやりたくありません)。

自分でパージを呼び出したり、アバター属性に null を割り当てたりしようとしましたが、うまくいきませんでした

def check_image_type
  if avatar.attached? && !avatar.content_type.in?(%("image/jpeg image/png"))
    errors.add(:avatar, "Invalid Avatar Format")
    avatar.purge    <---- Not working
    avatar = nil <--- Not working
    avatar = '' <--- Not working
  end
end

編集: 必ずしもアップロードしたばかりのプレビューを表示しようとしているわけではありません。実のところ、私はそんなことはしたくないのですが、Rails が勝手にやっているようです。

この例では、ユーザーを作成するときにアバターがないため何も表示されませんが、間違ったファイルタイプのアバターをアップロードしようとしてフォームがリロードされてエラーが表示されると、ロードに失敗したアバターを表示しようとします。アップロードされたファイルが正しいタイプの場合、ユーザー情報が保存され、ユーザー リストまたはロードされたアバターを表示できる別の画面にリダイレクトされます。

ユーザーがすでにアバターを持っており、それを変更したい場合。まずフォームを開くと (編集アクションを使用して)、現在のアバターが表示されます。それを変更して無効なファイルをアップロードしようとすると、再びエラーが発生してフォームがリロードされますが、現在の有効なアバターが空のアバターに置き換えられます(データベースであっても、古いアバターはまだ残っています)。同様に、有効なファイルをアップロードすると、フォームが送信され、アバターが変更され、次の画面に表示されます。

要約すると、適切な行動は次のとおりです (私はそう感じています) 検証 (ファイルの種類、サイズなど) に基づいて拒否されたファイルをアップロードしようとした場合、Rails はファイルのアップロードを試みていないかのように動作するはずです。 「暫定的な添付ファイル」の残骸はすべて廃棄する必要があります。

新しいリソースの場合は、アバターは表示されませんが、既存のリソースの場合は、現在のアバターが表示されます。

解決策

エラーを防ぐために、persisted を使用できますか?

<% if person.avatar.attached? && person.avatar.persisted? %>
  <%= image_tag(person.avatar)%>
  Remove <%= form.check_box :remove_avatar%>
<%end%>

そして、この gem を ActiveStorage の検証に使用できます。

このような:

validates :avatar, content_type: %w[image/png image/jpg image/jpeg]