我设置了一个story
模型,其中包含由Paperclip处理的image
附件,如下所示:
class Story < ActiveRecord::Base
has_attached_file :image # [...]
attr_accessible :user_id, :title, :image, :image_file_name
belongs_to: user
validates_presence_of :user_id
validates :title, :length => { :maximum => 50 }
validates_attachment_size :image, :less_than => 2.megabytes, :unless => Proc.new { |story| story[:image].nil? }
# [...]
end
当我填写我的故事表格时,看起来像:
<%= form_for @story, html: { multipart: true } do |f| %>
<% if @story.errors.any? %>
<div id="error-explanation">
<ul>
<% @story.errors.full_messages.each do |msg| %>
<li class="error-mess">Error: <%= msg.downcase %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.text_field :title %></td>
<%= f.file_field :image %>
<%= f.submit t('.send') %>
<% end %>
如果对于story.title的验证失败太长,则表单会正确地重新显示,并且正确的错误消息和已填写无效的标题,但是file_field
是现在为空,我必须再次点击它才能重新选择我要上传的文件。
这就是我的 stories_controller.rb 的样子:
def create
@story = @current_user.stories.new(params[:story])
if @story.save
redirect_to thanks_path
else
# !@story.save so I render action 'new' again just to
# bang my head against this 'anomaly'
render action: "new"
end
end
如何避免用户在验证错误后重新选择要上传的文件?
答案 0 :(得分:5)
HTTP文件上传的方式在浏览器中工作,该文件已经在首次提交时已经上传到您的应用程序 - 因此您应该将其存储在某个位置,以便您稍后在第二次表单提交时仍然可以访问它。 (至少在PHP中,上传的文件在脚本运行后被删除,如果它没有被明确地移动到其他地方 - 我不知道这是否也适用于RoR。)
出于安全原因,您无法预先填写HTML中的输入类型=文件字段。即使用户再次选择文件,他们也必须再次发送文件 - 浪费用户和带宽。
因此,要么在首次提交时将其存储在某个位置,要么在允许提交(尽可能)之前尝试使用JavaScript在客户端进行验证,以便最小化表单提交实际上在服务器端验证失败。
答案 1 :(得分:2)
可能正在使用https://github.com/bcardarella/client_side_validations将适合您。它允许根据模型中定义的规则执行表单验证,而无需重新加载页面。
答案 2 :(得分:1)
CBroe是对的,最好的解决方案是临时存储文件。我要做的是: - 将文件移动到临时目录,并使用尝试上载它的用户的ID命名。 - 当发布表单并且没有上载文件时,尝试使用该用户的临时文件(如果存在)。 - 如果故事已成功保存,请删除该用户的所有临时文件。
我认为应该这样做。
答案 3 :(得分:1)
我不得不在最近的一个项目中解决这个问题。它有点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: ''}