8

我设置了一个带有由 Paperclip 处理story的附件的模型,如下所示: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

如何避免用户在验证错误后重新选择要上传的文件?

4

3 回答 3

5

HTTP 文件上传在浏览器中的工作方式,文件已经在第一次提交时上传到你的应用程序——所以你应该把它存储在某个地方,这样你以后仍然可以在第二次提交表单时访问它。(至少在 PHP 中,一个上传的文件在脚本运行后会被删除,如果它没有被明确地移动到其他地方——我不知道这是否也适用于 RoR。)

出于安全原因,您不能在 HTML 中预先填写 input type=file 字段。即使用户再次选择文件,他们也必须再次发送——浪费用户和您的带宽。

因此,要么在第一次提交时将其存储在某处,要么在允许提交(尽可能)之前尝试在客户端也使用 JavaScript 进行验证,以便最大限度地减少实际上在服务器端验证失败的表单提交。

于 2012-06-24T18:29:30.267 回答
1

CBroe 是对的,最好的解决方案是临时存储文件。我要做的是: - 将文件移动到临时目录,并使用尝试上传它的用户的 ID 命名。- 当表单发布并且没有上传文件时,尝试使用该用户的临时文件(如果存在)。- 如果故事成功保存,请删除该用户的所有临时文件。

我认为这应该可以解决问题。

于 2012-06-28T18:19:11.690 回答
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: ''}
于 2014-05-16T22:22:56.987 回答