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的行为变化:
- Turbo表单提交逻辑:Rails 7默认启用Turbo,当表单以
TURBO_STREAM格式提交时,即便文件输入框未选择文件,Turbo也会将其作为空字符串数组([""])提交;而Rails 6的传统表单不会提交未选择文件的字段。 - 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
相关产品推荐
相关产品推荐

