可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have form with multiple file uploads, The issue is when i am submitting the form and an validation error occurs, the file input field gets reset.
I basically wanted to persist those files inside the file input field for the complete process.
I have also gone through few links
How can I "keep" the uploaded image on a form validation error?
Please let me know what are the various options in such cases that one can follow.
回答1:
Carrierwave is a great tool for handling file uploads and can handle this for you
https://github.com/jnicklas/carrierwave#making-uploads-work-across-form-redisplays
回答2:
Created a repo with a example of using Paperclip on rails and mainting your files when validation error occurs
https://github.com/mariohmol/paperclip-keeponvalidation
回答3:
I had to fix this on a recent project using the Paperclip Gem. It's a bit hacky but it works. I've tried calling cache_images() using after_validation and before_save in the model but it fails on create for some reason that I can't determine so I just call it from the controller instead. Hopefully this saves someone else some time!
model:
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
controller:
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
view:
= f.file_field :logo
= f.hidden_field :logo_cache
- if @shop.logo.file?
%img{src: @shop.logo.url, alt: ''}
回答4:
Well - I thought of taking a different approach to this; Instead of temporarily storing the file on the server, why not serve it back to the client to be resubmitted when the user fixes the validation issues.
This might still need a bit of refinement but it's the general concept:
# 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]
回答5:
I took a completely different approach to the other solutions on offer here, as I didn't fancy switching to CarrierWave or using yet another gem to implement a hack to get around this.
Basically, I define placeholders for validation error messages and then make an AJAX call to the relevant controller. should it fail validation I simply populate the error message placeholders - this leaves everything in place client side including the file input ready for resubmission.
Example follows, demonstrating an organisation with nested address model and a nested logo model (that has a file attachment) - this has been cut for brevity :
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
organisations/validation_errors.js.erb
$('.error_explanation').html('');
<% @organisation.errors.messages.each do |attribute, messages| %>
$('.<%= attribute %>.error_explanation').html("<%= messages.map{|message| "'#{message}'"}.join(', ') %>");
<% end %>
回答6:
A workaround for this rather than an outright solution is to use client side validation so that the file isn't lost because the whole form persists.
The few users that don't have JavaScript enabled will lose the files between requests, but perhaps this % is so low for you as to make it an acceptable compromise. If this is the route you decide to go down I'd recommend this gem
https://github.com/bcardarella/client_side_validations
Which makes the whole process really simple and means you don't have to rewrite your validation in JavaScript