Active Storage:表单重显时保留/缓存已上传文件的最佳实践
在Active Storage中表单重显时保留已上传文件的方案
这个问题我之前也踩过坑——Active Storage默认确实没有像Shrine的CachedAttachmentData那样开箱即用的缓存插件,但咱们可以通过session暂存Blob信息+模型虚拟属性的方式实现类似效果,让表单验证失败重显时,已上传的文件不会凭空消失。
下面分步骤详细说:
核心思路
当表单提交后验证失败时,把已上传文件对应的Active Storage Blob的ID暂存到session里;当重新渲染表单时,从session取出Blob ID,重新关联到模型对象上,同时在表单中保留已上传文件的展示和缓存标记,避免用户重复上传。
1. 控制器层处理缓存逻辑
首先修改控制器的new和create方法,负责缓存和恢复Blob信息:
def new @model = Model.new # 从session取出缓存的Blob,重新关联到模型 if session[:cached_model_blob_id].present? begin blob = ActiveStorage::Blob.find(session[:cached_model_blob_id]) @model.file.attach(blob) rescue ActiveRecord::RecordNotFound session.delete(:cached_model_blob_id) # 处理Blob已被删除的情况 end end end def create @model = Model.new(model_params) if @model.save # 成功保存后清除session缓存,避免下次进入new页面还显示旧文件 session.delete(:cached_model_blob_id) redirect_to @model, notice: "保存成功" else # 验证失败时,若已有文件上传,缓存Blob ID到session if @model.file.attached? session[:cached_model_blob_id] = @model.file.blob.id end render :new end end
2. 模型层添加虚拟属性处理缓存
给模型添加一个虚拟属性(比如file_cache),用来接收表单传递的缓存Blob ID,在验证前重新关联文件:
class Model < ApplicationRecord has_one_attached :file # 虚拟属性,用于接收缓存的Blob ID attr_accessor :file_cache before_validation do # 如果缓存ID存在且模型未关联文件,重新关联 if file_cache.present? && !file.attached? begin blob = ActiveStorage::Blob.find(file_cache) self.file.attach(blob) rescue ActiveRecord::RecordNotFound self.file_cache = nil # 忽略无效的Blob ID end end end end
3. 表单层展示已上传文件并传递缓存标记
修改表单,添加已上传文件的展示区域,同时用隐藏字段传递缓存的Blob ID,确保表单提交时能识别缓存的文件:
<%= form_with model: @model do |form| %> <%# 显示已上传的文件 %> <% if @model.file.attached? %> <div class="uploaded-file"> <p>已上传文件:<%= link_to @model.file.filename, rails_blob_path(@model.file, disposition: :attachment) %></p> <%# 隐藏字段传递缓存的Blob ID %> <%= hidden_field_tag 'model[file_cache]', @model.file.blob.id %> </div> <% end %> <%# 文件上传字段 %> <div class="field"> <%= form.label :file %> <%= form.file_field :file %> </div> <%# 其他表单字段 %> <div class="field"> <%= form.label :name %> <%= form.text_field :name %> <%= form.error_message_for :name %> </div> <div class="actions"> <%= form.submit "提交" %> </div> <% end %>
4. 允许参数中的缓存字段
在控制器的参数白名单中添加file_cache:
private def model_params params.require(:model).permit(:name, :file, :file_cache) end
针对多文件上传(has_many_attached)的适配
如果你的模型是多文件上传(has_many_attached :files),只需要稍微调整逻辑,把session存储的单个Blob ID改成数组即可:
控制器修改:
def new @model = Model.new if session[:cached_model_blob_ids].present? session[:cached_model_blob_ids].each do |blob_id| begin blob = ActiveStorage::Blob.find(blob_id) @model.files.attach(blob) rescue ActiveRecord::RecordNotFound next end end end end def create @model = Model.new(model_params) if @model.save session.delete(:cached_model_blob_ids) redirect_to @model, notice: "保存成功" else if @model.files.attached? session[:cached_model_blob_ids] = @model.files.blobs.pluck(:id) end render :new end end
模型修改:
class Model < ApplicationRecord has_many_attached :files attr_accessor :files_cache before_validation do if files_cache.present? && files.attached?.blank? files_cache.each do |blob_id| begin blob = ActiveStorage::Blob.find(blob_id) self.files.attach(blob) rescue ActiveRecord::RecordNotFound next end end end end end
表单修改:
<% if @model.files.attached? %> <% @model.files.each do |file| %> <div class="uploaded-file"> <p>已上传:<%= link_to file.filename, rails_blob_path(file, disposition: :attachment) %></p> <%= hidden_field_tag 'model[files_cache][]', file.blob.id %> </div> <% end %> <% end %>
注意事项
- Session清理:成功提交表单后一定要删除session中的缓存键,避免用户下次进入表单页面时加载旧文件。
- Blob有效性:添加
rescue ActiveRecord::RecordNotFound处理Blob已被删除的异常,避免页面报错。 - 存储时效:如果使用的是云存储(比如S3),确保Blob的生命周期配置不会在短时间内删除临时文件,否则缓存会失效。
内容的提问来源于stack exchange,提问作者cseelus
相关产品推荐
相关产品推荐

