You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 7.1更新操作中如何避免空附件触发原有附件被删除?

Rails 7.1迁移:未上传新文件时ActiveStorage附件被意外清除

现象

将Rails 6.0应用迁移到7.1版本后,更新ArchiveItem记录时,若用户未上传新的content_files,原有附件会被自动删除。

请求日志显示空参数被传入

Started PATCH "/archive_items/75" for 127.0.0.1 at 2024-01-16 10:33:53 -0800
10:33:53 web.1  | Processing by ArchiveItemsController#update as TURBO_STREAM
10:33:53 web.1  |   Parameters: {"authenticity_token"=>"[FILTERED]", "archive_item"=>{ "content_files"=>[""]}

数据库日志显示附件被删除

10:59:04 web.1  |   ActiveStorage::Attachment Load (0.3ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3  [["record_id", 75], ["record_type", "ArchiveItem"], ["name", "content_files"]]
10:59:04 web.1  |   ↳ app/controllers/archive_items_controller.rb:174:in `update'
10:59:04 web.1  |   ActiveStorage::Attachment Destroy (0.4ms)  DELETE FROM "active_storage_attachments" WHERE "active_storage_attachments"."id" = $1  [["id", 401]]
10:59:04 web.1  |   ↳ app/controllers/archive_items_controller.rb:174:in `update'

与Rails 6.0的差异

在原Rails 6.0应用中:

  • 用户未上传新文件时,content_files参数不会出现在请求中
  • 控制台调用.update方法时,不会删除现有附件

临时解决方案

通过修改控制器update动作,检查content_files是否为空数组并移除该参数:

def update
  @archive_item = ArchiveItem.find(params[:id])

  # 创建新参数哈希,移除空的content_files和medium_photos
  update_params = archive_item_params

  # 若content_files为空则从参数中删除
  if archive_item_params[:content_files] == [""]
    update_params.delete(:content_files)
  end

  @archive_item.update(update_params)

  flash.alert = "条目已更新。"
  redirect_to archive_items_path
end

def destroy
  @archive_item = ArchiveItem.find(params[:id])
  @archive_item.destroy

  redirect_to archive_items_path
end

private

def archive_item_params
  params.require(:archive_item).permit(
    :poster_image, :title, :medium, :year, :credit, :location, 
    :tag_list, :location_list, :person_list, :comm_group_list, 
    :collection_list, :date_is_approx, :content_notes, :medium_notes, 
    :medium_photo, :search_tags, :search_locations, :search_people, 
    :search_comm_groups, :search_collections, :created_by, :updated_by, 
    :updated_at, content_files: [], medium_photos: []
  )
end

疑问:为何未上传文件时会传入空参数?

对应的表单字段代码如下:

<div class="form-row">
  <% if @archive_item.content_files.attached? %>
    <div class="form-set">
      <label>Content Preview</label>
        <% @archive_item.content_files.each do |file| %>
          <% if @archive_item.medium == "photo" %>
            <% if file.content_type == "image/jpeg" || file.content_type == "image/png" %>
              <%= image_tag file.variant(resize_to_limit: [600, nil]), class: "admin-form-image" %>
            <% else %>
              <%= link_to 'View file', file, target: "_blank", class: "download-link" %>
            <% end %>
          <% else %>
              <div class="admin-form-filename">File: <%= file.filename %></div>
          <% end %>
        <% end %>
    </div>
  <% end %>
  <div class="form-set">
    <%= form.label :content_files, "Content File" %>
    <%= form.file_field :content_files, direct_upload: true, multiple: true %>
  </div>
</div>

原因分析

该问题源于Rails 7+中Turbo与ActiveStorage的行为变化:

  1. Turbo表单提交逻辑:Rails 7默认启用Turbo,当表单以TURBO_STREAM格式提交时,即便文件输入框未选择文件,Turbo也会将其作为空字符串数组([""])提交;而Rails 6的传统表单不会提交未选择文件的字段。
  2. ActiveStorage参数处理:当content_files: []作为参数传入时,ActiveStorage会将其视为“清空现有附件”的指令——空数组明确表示要将关联附件集合置空;而Rails 6中参数不存在时,ActiveStorage会保留原有附件。

更优雅的解决方案

可以在参数过滤方法中直接清理空文件参数,避免在update动作中单独处理:

private

def archive_item_params
  filtered_params = params.require(:archive_item).permit(
    :poster_image, :title, :medium, :year, :credit, :location, 
    :tag_list, :location_list, :person_list, :comm_group_list, 
    :collection_list, :date_is_approx, :content_notes, :medium_notes, 
    :medium_photo, :search_tags, :search_locations, :search_people, 
    :search_comm_groups, :search_collections, :created_by, :updated_by, 
    :updated_at, content_files: [], medium_photos: []
  )

  # 移除全为空的文件数组参数
  filtered_params.delete(:content_files) if filtered_params[:content_files]&.all?(&:blank?)
  filtered_params.delete(:medium_photos) if filtered_params[:medium_photos]&.all?(&:blank?)

  filtered_params
end

此时控制器update动作可恢复简洁:

def update
  @archive_item = ArchiveItem.find(params[:id])
  @archive_item.update(archive_item_params)
  flash.alert = "条目已更新。"
  redirect_to archive_items_path
end

内容的提问来源于stack exchange,提问作者Sam Bovarnick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:05:59