Эта проблема существует уже довольно давно, и по сей день я до сих пор не нашел решения. Я видел некоторые подобные проблемы, но не совсем то, с чем сталкивался.
Я испытывал 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 должен действовать так, как будто я даже не пытался загрузить файл. Это должно удалить любые остатки "предварительной привязанности".
В случае нового ресурса все равно не будет отображаться аватар, но в случае уже существующего ресурса он по-прежнему будет отображать текущий аватар.