【问题标题】:How to persist file upload fields after a rails validation error如何在 Rails 验证错误后保留文件上传字段
【发布时间】:2013-03-18 19:27:56
【问题描述】:

我有一个包含多个文件上传的表单,问题是当我提交表单并且发生验证错误时,文件输入字段被重置。

我基本上想将这些文件保存在文件输入字段中以完成整个过程。

我也浏览了几个链接

How can I "keep" the uploaded image on a form validation error?

请告诉我在这种情况下可以遵循的各种选择。

【问题讨论】:

  • 由于 Rails 5.2 支持使用 Active Storage 上传文件,现在有没有办法在表单重新“原生”显示后保留上传?
  • Rails 6.0 中的 ActiveStorage 最终解决了这个问题吗?
  • @cseelus 是的,我刚刚在这里找到了这篇文章,其中概述了如何解决这个问题:medium.com/earthvectors/… 请参阅解决方案 2 部分
  • 是的,Rails 6.0 以上默认处理这个问题,完全没有问题。

标签: ruby-on-rails validation file-upload


【解决方案1】:

Carrierwave 是处理文件上传的绝佳工具,可以为您处理此问题

https://github.com/jnicklas/carrierwave#making-uploads-work-across-form-redisplays

【讨论】:

  • 我在我的项目中使用 gem 'paperclip',有没有这样的回形针选项
  • 我自己没用过回形针,当然不是我用 CarrierWave 的原因之一
  • 如果您确实需要,您可以迁移到 CarrierWave。 github.com/jnicklas/carrierwave/wiki/…
  • 在这种情况下保持密码也是不可能的?对吗?
  • 这个答案在可接受的范围内,但它应该更具体地解释为什么这个特定工具对这项任务有用。不仅仅是“它适用于此”。
【解决方案2】:

创建了一个 repo,其中包含在 Rails 上使用 Paperclip 并在发生验证错误时维护文件的示例

https://github.com/mariohmol/paperclip-keeponvalidation

【讨论】:

  • 很好,但您是否设法使用文件处理嵌套对象?
  • 你没有说这个与现有项目的细节集成。它抛出了一些错误。
  • 发送 repo 或这里社区会帮助你
  • 请在此处提供答案,而不是 github 的链接。
【解决方案3】:

我不得不在最近的一个项目中使用 Paperclip Gem 解决这个问题。这有点hacky,但它有效。我尝试在模型中使用 after_validation 和 before_save 调用 cache_images() 但由于某种我无法确定的原因创建失败,所以我只是从控制器调用它。希望这可以节省其他人一些时间!

型号:

class Shop < ActiveRecord::Base    
  attr_accessor :logo_cache

  has_attached_file :logo

  def cache_images
    if logo.staged?
      if invalid?
        FileUtils.cp(logo.queued_for_write[:original].path, logo.path(:original))
        @logo_cache = encrypt(logo.path(:original))
      end
    else
      if @logo_cache.present?
        File.open(decrypt(@logo_cache)) {|f| assign_attributes(logo: f)}
      end
    end
  end

  private

  def decrypt(data)
    return '' unless data.present?
    cipher = build_cipher(:decrypt, 'mypassword')
    cipher.update(Base64.urlsafe_decode64(data).unpack('m')[0]) + cipher.final
  end

  def encrypt(data)
    return '' unless data.present?
    cipher = build_cipher(:encrypt, 'mypassword')
    Base64.urlsafe_encode64([cipher.update(data) + cipher.final].pack('m'))
  end

  def build_cipher(type, password)
    cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').send(type)
    cipher.pkcs5_keyivgen(password)
    cipher
  end

end

控制器:

def create
  @shop = Shop.new(shop_params)
  @shop.user = current_user
  @shop.cache_images

  if @shop.save
    redirect_to account_path, notice: 'Shop created!'
  else
    render :new
  end
end

def update
  @shop = current_user.shop
  @shop.assign_attributes(shop_params)
  @shop.cache_images

  if @shop.save
    redirect_to account_path, notice: 'Shop updated.'
  else
    render :edit
  end
end

查看:

= f.file_field :logo
= f.hidden_field :logo_cache

- if @shop.logo.file?
  %img{src: @shop.logo.url, alt: ''}

【讨论】:

    【解决方案4】:

    嗯 - 我想采取不同的方法来解决这个问题;与其将文件临时存储在服务器上,不如将其返回给客户端,以便在用户修复验证问题时重新提交。

    这可能还需要一些改进,但这是一般概念:

    # in the controller - save the file and its attributes to params
    
    def create
      # ...
      if params[:doc] # a regular file uploaded through the file form element
        # when the form re-renders, it will have those additional params available to it
        params[:uploaded_file] = params[:doc].read # File contents
        params[:uploaded_file_original_filename] = params[:doc].original_filename
        params[:uploaded_file_headers] = params[:doc].headers
        params[:uploaded_file_content_type] = params[:doc].content_type
      elsif params[:uploaded_file] # a file coming through the form-resubmit
        # generate an ActionDispatch::Http::UploadedFile
        tempfile = Tempfile.new("#{params[:uploaded_file_original_filename]}-#{Time.now}")
        tempfile.binmode
        tempfile.write CGI.unescape(params[:uploaded_file]) #content of the file / unescaped
        tempfile.close
    
        # merge into the params
        params.merge!(doc: 
           ActionDispatch::Http::UploadedFile.new(
                                    :tempfile => tempfile,
                                    :filename => params[:uploaded_file_original_filename],
                                    :head => params[:uploaded_file_headers],
                                    :type => params[:uploaded_file_content_type]
                               )
                     )
    
      end
      #...
      # params (including the UploadedFile) can be used to generate and save the model object
    end
    
    
    # in the form (haml)
    - if !params[:uploaded_file].blank?
      # file contents in hidden textarea element
      = text_area_tag(:uploaded_file, CGI.escape(params[:uploaded_file]), style: 'display: none;') #escape the file content
      = hidden_field_tag :uploaded_file_headers, params[:uploaded_file_headers]
      = hidden_field_tag :uploaded_file_content_type, params[:uploaded_file_content_type]
      = hidden_field_tag :uploaded_file_original_filename, params[:uploaded_file_original_filename]
    

    【讨论】:

    • 一定不错,不用花钱买带宽。
    • 我使用了这个,但附件无法打开。我刚刚删除了 tempfile.binmode 并且它可以工作。
    【解决方案5】:

    我对这里提供的其他解决方案采取了完全不同的方法,因为我不喜欢切换到 CarrierWave 或使用另一个 gem 来实施破解来解决这个问题。

    基本上,我为验证错误消息定义占位符,然后对相关控制器进行 AJAX 调用。如果验证失败,我只需填充错误消息占位符 - 这会将所有内容保留在客户端,包括准备重新提交的文件输入。

    示例如下,展示了具有嵌套地址模型和嵌套徽标模型(具有文件附件)的组织 - 为简洁起见,已将其删除:

    organisations/_form.html.erb

    <%= form_for @organisation, html: {class: 'form-horizontal', role: 'form', multipart: true}, remote: true do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>
      <p class='name error_explanation'></p>
    
      <%= f.fields_for :operational_address do |fa| %>
        <%= fa.label :postcode %>
        <%= fa.text_field :postcode %>
        <p class='operational_address postcode error_explanation'></p>
      <% end %>
    
      <%= f.fields_for :logo do |fl| %>
        <%= fl.file_field :image %>
        <p class='logo image error_explanation'></p>
      <% end %>
    <% end %>
    

    organisations_controller.rb

    def create   
      if @organisation.save
        render :js => "window.location = '#{organisations_path}'"
      else
        render :validation_errors
      end
    end
    

    组织/validation_errors.js.erb

    $('.error_explanation').html('');
    <% @organisation.errors.messages.each do |attribute, messages| %>
      $('.<%= attribute %>.error_explanation').html("<%= messages.map{|message| "'#{message}'"}.join(', ') %>");
    <% end %>
    

    【讨论】:

    • 但是不支持通过ajax上传文件,对吧,如果表单提交成功?
    • 很好的答案?
    【解决方案6】:

    解决此问题而不是彻底的解决方案是使用客户端验证,这样文件就不会因为整个表单持续存在而丢失。

    少数未启用 JavaScript 的用户会在请求之间丢失文件,但也许这个 % 对您来说太低以至于可以接受妥协。如果这是您决定走的路线,我会推荐这颗宝石

    https://github.com/bcardarella/client_side_validations

    这使得整个过程变得非常简单,意味着您不必在 JavaScript 中重写验证

    【讨论】:

      【解决方案7】:

      浏览器阻止在文件输入时设置 value 属性 出于安全原因键入,这样您就无法在没有 用户自己选择了任何文件。

      Pre-Populate HTML form file input

      您可以使用载波:https://github.com/carrierwaveuploader/carrierwave

      或者通过js请求验证模型。

      【讨论】:

      • 你能不能让我参考规定这个的规范?
      猜你喜欢
      • 1970-01-01
      • 2011-08-24
      • 2011-09-06
      • 1970-01-01
      • 1970-01-01
      • 2017-03-13
      • 2023-03-18
      • 1970-01-01
      • 2012-08-22
      相关资源
      最近更新 更多