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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:09:16