ruby Active Storage即使在验证失败后仍然检测到附件

dfty9e19  于 2023-06-22  发布在  Ruby
关注(0)|答案(3)|浏览(95)

这是一个已经持续了很长一段时间的问题,直到今天,我仍然没有找到解决方案。我看到过一些类似的问题,但不完全是我所经历的。
自从Rails Active Storage在Rails 5中出现以来,我一直在使用它,但由于这个特殊的问题,我从未在生产中实际使用过它。主要的问题是,当Rails 6出来时,如果你对文件附件进行任何验证,记录不会保存,但附件(blob)仍然会保存,如果你的验证是在内容类型上(确保它是JPEG图像),那么你最终会得到一个无效的附件(如果你上传了一个文本文件)。例如,如果您试图用image_tag“显示”此附件,则会导致问题。Rails6解决了这个问题,它只在记录实际保存到数据库中时才保存附件。这只解决了我一半的问题。
这就是我现在所经历的,还没有找到解决方案。
假设您有一个非常基本的设置。带有姓名、电子邮件和附加头像的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

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

预期的行为是

  • 当创建一个新的人,没有头像显示(这工程)
  • 通过选择适当的文件类型(jpg或png),您可以保存Person(这是可行的)
  • 通过选择一个错误的文件类型,你不能保存的人,可以选择另一个文件(不工作)

当您选择错误的文件类型时,它确实会阻止记录被保存,这很好,但它仍然会“看到”非持久化记录上的附件。因此,尽管在创建操作中最初显示表单时没有显示任何头像,但在验证失败后重新呈现表单时,会显示一个“空”头像。这是由于附件?方法返回true,即使记录上没有附件(因为它被拒绝了)。
有一个空白的头像只是看起来有点时髦,它看起来像一个图片,有一个断开的链接。但是,如果您要对头像本身进行任何操作,例如Person类中的thumbnail方法,则会生成以下错误:ActiveStorage::InvariableError
这是由于附件?属性为真并且化身属性有效,但是它不具有相关联的斑点(图片)。因此,尝试将其调整为缩略图会失败并出现错误。
我试图找到一种方法来"清除"或重置头像和/或附加?当验证阻止保存记录时,将返回属性。在内部,Rails做了它应该做的事情(不保存文件,如果有当前文件,则保留当前文件)。
但是表单上显示的内容(如果您显示头像)一定会让用户感到困惑,特别是如果用户在您选择新头像之前就已经有了头像的话。如果你选择了一个无效的头像,而表单拒绝了它,那么在重新渲染时,你的初始头像将不会显示,取而代之的是图片断开的链接图标。这可能会使用户感到困惑,以为他们之前的化身已经被消灭,而事实并非如此。在这一点上,我不知道如何解决这个问题,而不深入到活动存储的胆量(我不感兴趣,现在这样做)。
我试过自己调用purge或者给avatar属性赋值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应该表现得好像我甚至没有尝试上传文件一样。它应该废除任何“暂时性联系”的残余。
在新资源的情况下,它仍然不显示化身,但是在已经存在的资源的情况下,它仍然显示当前化身。

ct2axkht

ct2axkht1#

要防止错误,可以使用persisted?

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

您可以使用this gem进行ActiveStorage验证。
像这样:

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

ccgok5k52#

我发现的另一种解决方法是使用variable?
如果ImageMagick可以转换blob(其内容类型为ActiveStorage.variable_content_types),则返回true
它仍然不工作,如果你试图覆盖一个现有的图像使用一个无效的文件虽然-预览旧文件仍然会消失,其他人已经注意到在其他答案。

2ul0zpep

2ul0zpep3#

我刚刚找到了一些解决方法,就用reload方法

<%= render "settings/profiles/form/avatar", avatar: profile.reload.avatar %>
<% if avatar.attached? %>
  <%= image_tag avatar.variant(:default) %>
<% else %>
  <%= image_tag "avatar.png", width: 128, height: 128 %>
<% end %>

则当avatar无效时,对于上传的文件出现错误,但是如果还没有附加,则显示先前的有效头像或空白头像。

相关问题