Active Storage по-прежнему обнаруживает вложение даже после сбоя проверки - PullRequest
1 голос
/ 05 февраля 2020

Эта проблема существует уже довольно давно, и по сей день я до сих пор не нашел решения. Я видел некоторые подобные проблемы, но не совсем то, с чем сталкивался.

Я испытывал Rails Active Storage с момента его появления в Rails 5, но так и не смог использовать его на практике из-за этой конкретной проблемы. Основная проблема, которая была решена, когда вышел Rails 6, заключалась в том, что если вы внедрите какую-либо проверку для вложения файла, запись не сохранится, но вложение (blob) все равно будет сохранено, и если ваша проверка была для типа контента ( например, убедитесь, что это изображение JPEG), и в результате вы получили недопустимое вложение (например, если вы загрузили текстовый файл) Это может вызвать проблемы, если вы попытаетесь «отобразить» это вложение, например, с помощью image_tag. Rails 6 решает эту проблему, сохраняя вложение только тогда, когда запись фактически сохраняется в базе данных. Это только устраняет половину проблемы для меня.

Вот то, что я все еще испытываю и пока не нашел решения.

Предположим, у вас очень базовая c настройка. Модель 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), вы можете сохранить человека (это работает)
  • Выбрав неправильный тип файла, вы не можете сохранить человека и можете выберите другой файл (не работает)

Что происходит, когда вы выбираете неправильный тип файла, это предотвращает сохранение записи, и это нормально, но все равно «видит» вложение на сохраненная запись. Таким образом, хотя аватар не отображался, когда форма первоначально отображалась в действии create, «пустой» аватар отображается, когда форма повторно отображается после неудачной проверки. Это связано с прикрепленным? метод, возвращающий true, даже если в записи нет вложения (так как он был отклонен).

Наличие пустого аватара выглядит просто немного странно, похоже на картинку с неработающей ссылкой. Тем не менее, если вы будете выполнять какие-либо манипуляции с самим аватаром, такие как метод миниатюр в классе Person, это приведет к следующей ошибке: ActiveStorage :: InvariableError

Это связано с прикрепленным? атрибут true и атрибут avatar действителен, но с ним нет связанного большого двоичного объекта (рисунок). Так что попытка изменить его размер до миниатюры завершается неудачно с ошибкой.

Я пытаюсь найти способ «очистить» или сбросить аватар и / или присоединить? атрибуты, когда проверка препятствует сохранению записи. Внутри Rails делает то, что должен (не сохраняя файл и не сохраняя текущий, если он есть).

Но то, что показано в форме (если вы отображаете аватар), может запутать пользователя, особенно если у пользователя был аватар, прежде чем вы выбрали новый. Если вы выберете неверный аватар и форма отклонит его, то при повторном рендеринге ваш первоначальный аватар не будет отображаться и будет заменен значком прерванной ссылки. Это может сбить с толку пользователя, думая, что его предыдущий аватар был уничтожен, хотя это не так. На данный момент я не уверен, как это исправить, не вдаваясь в подробности активного хранилища (что мне сейчас не интересно).

Я пытался вызвать очистку самостоятельно или присвоить 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

РЕДАКТИРОВАТЬ: я не обязательно пытаюсь отобразить предварительный просмотр того, что только что загрузил , На самом деле, я не хочу этого делать, но, похоже, Rails делает это сам по себе.

В моем примере, когда вы создаете пользователя, нет аватара, поэтому никто не показывает, но когда вы пытаетесь загрузить аватар неправильного типа файла, и форма перезагружается, чтобы показать ошибку, он пытается отобразить неудачно загруженный аватар. Если загруженный файл имеет правильный тип, он сохраняет информацию о пользователе и перенаправляет в список пользователей или на другой экран, где мы можем отобразить загруженный аватар.

В случае, когда у пользователя уже есть аватар и Вы хотите изменить это. Сначала вы открываете форму (с действием редактирования), и она показывает текущий аватар. Если я пытаюсь изменить его и загрузить неверный файл, форма снова перезагружается с ошибкой, но снова заменяет текущий действующий аватар пустым (даже если в базе данных старый файл все еще остается). Таким же образом, если я загружу действительный файл, тогда форма будет отправлена, аватар изменится, и мы увидим это на следующем экране.

Подводя итог, что (я чувствую) правильное поведение должно быть следующим: если я пытаюсь загрузить файл, отклоненный на основе проверки (тип файла, размер и т. д. c), тогда Rails должен действовать так, как будто я даже не пытался загрузить файл. Это должно удалить любые остатки "предварительной привязанности".

В случае нового ресурса все равно не будет отображаться аватар, но в случае уже существующего ресурса он по-прежнему будет отображать текущий аватар.

1 Ответ

1 голос
/ 06 февраля 2020

Для предотвращения ошибок вы можете использовать persisted?

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

И вы можете использовать этот самоцвет для проверки ActiveStorage.

Например:

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