2012-06-22 69 views
8

我成立了一个story模型由回形针处理的image附件后的“新”行动,看起来像文件路径:回形针(3.0)和Rails(3.2):f.file_field失去一个验证错误

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过长形式沿着正确与正确的错误消息无效TITL重新显示失败e已经填写,,但file_field现在是空白的,我必须再次点击它才能重新选择我想上传的文件

这里是我的stories_controller.rb的样子:

def create 
    @story = @current_user.stories.new(params[:story]) 
    if @story.save                             
    redirect_to thanks_path  
    else 
    # [email protected] so I render action 'new' again just to 
    # bang my head against this 'anomaly' 
    render action: "new" 
    end    
end 

我怎样才能避免重新选择文件验证错误后上传用户?

+0

我面临同样的问题,你有没有找到一个解决办法?谢谢! – yorch

+0

只有一个图形:选择图片时,我通过jQuery提供UI反馈。假设我的图像字段是由'<%= f.file_field:image%>生成的,我可以使用下面的jQuery来触发它:'$(“#story_image”)。change(function(){$(“#image_selected “).show(300);});'然后在我看来,我有一个隐藏的div”image_selected“,其中包含一个雄辩的gif ...如果您发现_real_解决方案,请告诉我! – Darme

回答

5

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

不能 pre - 为了安全起见,在HTML中填充输入类型=文件字段。即使用户再次选择文件,他们也必须再次发送 - 浪费用户和您的带宽。

因此,无论什么地方存放起来先提交,或尝试允许提交之前做你的客户端用JavaScript也验证(尽可能),这样就最大限度地减少表单提交的实际验证失败在服务器端。

+2

好的,现在的问题是:如何在RoR应用程序中检索上一个请求中上传的文件? :-)感谢您提供有关安全原因的提示,但我并没有完全意识到这一点。 – Darme

1

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

我认为应该这样做。

+1

所以问题是:我怎么能在Rails上做到这一点? – Darme

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: ''}