即使验证失败后,Active Storage 仍会检测附件

Dan*_*Dan 8 ruby ruby-on-rails rails-activestorage ruby-on-rails-6

这个问题已经持续了很长一段时间了,直到今天,我仍然没有找到解决方案。我见过一些类似的问题,但不完全是我所经历的。

自从 Rails Active Storage 在 Rails 5 中推出以来,我一直在体验它,但由于这个特殊问题,我从未在生产中实际使用它。Rails 6 发布时解决的主要问题是,如果您对文件附件实施任何验证,记录将不会保存,但附件(blob)仍会被保存,并且如果您的验证是针对内容类型(例如,确保它是 JPEG 图像),然后您最终会得到无效的附件(例如,如果您上传了文本文件)。例如,如果您尝试使用 image_tag“显示”此附件,这可能会导致问题。Rails 6 通过仅在记录实际保存到数据库中时才保存附件来解决此问题。这对我来说只能解决一半的问题。

这是我仍然遇到的情况,尚未找到解决方案。

假设您有一个非常基本的设置。具有姓名、电子邮件和附加头像的 Person 模型

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 
Run Code Online (Sandbox Code Playgroud)

正如您在上面的代码中看到的,该人有一个附加的头像。保存后,我们验证图像类型,确保它是 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

Run Code Online (Sandbox Code Playgroud)

然后在表单中,如果头像存在于表单顶部,我会显示该头像,然后让用户创建/编辑信息并根据需要选择另一个头像。

<%= 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 %>

Run Code Online (Sandbox Code Playgroud)

预期的行为是

  • 创建新人时,不显示头像(这有效)
  • 通过选择适当的文件类型(jpg 或 png),您可以保存人物(这有效)
  • 通过选择错误的文件类型,您将无法保存此人,并且可以选择另一个文件(不起作用)

发生的情况是,当您选择错误的文件类型时,它确实会阻止保存记录,这很好,但它仍然“看到”非持久记录上的附件。因此,尽管在创建操作中最初显示表单时没有显示头像,但在验证失败后重新呈现表单时,会显示“空”头像。这是由于附件吗?即使记录上没有附件(因为它被拒绝),方法也会返回 true。

空白头像看起来有点时髦,看起来就像一张链接断开的图片。但是,如果您要对头像本身进行任何操作,例如 Person 类中的缩略图方法,则会生成以下错误:ActiveStorage::InvariableError

这是由于附件吗?属性为 true 并且头像属性有效,但没有关联的 blob(图片)。因此,尝试将其大小调整为缩略图会失败并出现错误。

我正在尝试找到一种方法来“清除”或重置头像和/或附加的?当验证阻止记录保存时的属性。在内部,Rails 执行它应该执行的操作(不保存文件并保留当前文件(如果有))。

但是表单上显示的内容(如果您显示头像)必然会让用户感到困惑,特别是如果用户在您选择新头像之前已有头像。如果您选择了无效的头像并且表单拒绝了它,则在重新渲染时,您的初始头像不会显示,而是会替换为图片损坏的链接图标。这可能会让用户误以为他们之前的头像已被删除,但事实并非如此。目前,我不确定如何在不深入活动存储内部的情况下解决这个问题(我现在对此不感兴趣)。

我尝试自己调用 purge 或将 null 分配给 avatar 属性,但这不起作用

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
Run Code Online (Sandbox Code Playgroud)

编辑:我不一定要尝试显示我刚刚上传的内容的预览。事实上,我不想这样做,但 Rails 似乎自己在这样做。

在我的示例中,当您创建用户时,没有头像,因此没有显示任何头像,但是当您尝试上传错误文件类型的头像并且表单重新加载以显示错误时,它会尝试显示加载失败的头像。如果上传的文件类型正确,它会保存用户信息并重定向到用户列表或另一个屏幕,我们可以在其中显示加载的头像。

当用户已有头像而您想要更改它时。您首先打开表单(使用编辑操作),它会显示当前的头像。如果我尝试更改它并再次上传无效文件,表单将重新加载并出现错误,但再次将当前有效的头像替换为空头像(即使我是数据库,旧的头像仍然存在)。同样,如果我上传有效文件,则表单将提交,头像将更改,我们将在下一个屏幕中看到它。

总而言之,(我认为)正确的行为应该是,如果我尝试上传一个基于验证(文件类型、大小等)而被拒绝的文件,那么 Rails 应该表现得好像我什至没有尝试过上传一个文件一样。文件。它应该废弃“暂定附件”的任何残留物。

对于新资源,它仍然不会显示头像,但对于已经存在的资源,它仍然会显示当前头像。

mec*_*cov 10

为了防止错误,您可以使用persisted?

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

您可以使用此 gem进行 ActiveStorage 验证。

像这样:

validates :avatar, content_type: %w[image/png image/jpg image/jpeg]
Run Code Online (Sandbox Code Playgroud)

  • 当验证失败时,这会处理空附件(或损坏的链接图像)。所以这适用于新资源。但是,当用户已有头像并且您尝试保存时,如果新头像验证失败,则不会显示旧头像。我可能必须为此研究一种解决方案。 (3认同)
  • 此解决方案部分起作用:它可以防止显示损坏的图像,但是如果存在以前的头像并且在更新期间存在验证错误(在任何字段上),则不会显示头像(预期的行为是旧头像,仍附加,应显示)。 (2认同)