如何在rails验证错误后保留文件上载字段。

时间:2013-03-28 11:22:07

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

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

我基本上想在文件输入字段中保留这些文件以进行整个过程。

我也经历过几个链接

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

请告诉我在这种情况下可以遵循的各种选项有哪些。

7 个答案:

答案 0 :(得分:9)

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

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:2)

我不得不在最近使用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: ''}

答案 3 :(得分:2)

嗯 - 我想过对此采取不同的方法;而不是暂时将文件存储在服务器上,为什么不在用户修复验证问题时将其提供给客户端以重新提交。

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

# 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]

答案 4 :(得分:1)

我对这里提供的其他解决方案采取了完全不同的方法,因为我不想转换到CarrierWave或使用另一个gem来实现黑客来解决这个问题。

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

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

<强>组织/ _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 %>

答案 5 :(得分:0)

这个而不是彻底解决方案的解决方法是使用客户端验证,以便文件不会丢失,因为整个表单仍然存在。

没有启用JavaScript的少数用户将丢失请求之间的文件,但是这个%对于您而言可能是如此之低,以至于使其成为可接受的折衷方案。如果这是你决定要走的路线,我会推荐这个宝石

https://github.com/bcardarella/client_side_validations

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

答案 6 :(得分:0)

  

浏览器禁止在文件输入时设置value属性   为安全起见,请键入,以便您在没有   用户选择了任何文件本身。

     

Pre-Populate HTML form file input

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

或通过js请求验证模型。