Paperclip (3.0) and Rails (3.2): f.file_field loses the file path in the "new" action after a validation error

I installed the story model with an image application processed by Paperclip, which looks like this:

 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 

When I fill out my story, it looks like this:

 <%= 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 %> 

If the check is too long for story.title, the form displays correctly again with the correct error message and an invalid header, but file_field now empty and I have to click it again to re-select the file I want to upload .

And this is what my story_controller.rb looks like:

 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 

How can I avoid the user re-selecting the file to download after a validation error?

+8
ruby-on-rails file-upload paperclip
source share
4 answers

The way to download HTTP files in browsers, the file was added to your application the first time it was sent - so you should store it somewhere, so that you have access to it later on the second submit form. (At least in PHP, the downloaded file is deleted after running the script, if it is not explicitly moved to another place - I do not know if this is applicable for RoR.)

You cannot prefill the input field = file field in HTML - for security reasons. And even if the user picks up the file again, they will have to send it a second time - a waste of users and your bandwidth.

So either store it somewhere on the first submission, or try to perform your client-side checks also using JavaScript before allowing submission (as much as possible) so that you minimize the submission of forms that do not actually confirm the check on the server side.

+5
source share

Maybe using https://github.com/bcardarella/client_side_validations will be suitable for you. It allows you to perform form validation in accordance with the rules defined in your model without reloading the page.

+2
source share

CBroe is right, the best solution is to temporarily store the file. What I will do to do this: - Move the file to the temp directory and name it the ID of the user who tried to download it. - When the form is submitted and the file is not uploaded, try using a temporary file for this user (if one exists). - If the history was saved successfully, delete any temporary files for this user.

I think this was supposed to do the trick.

+1
source share

I had to fix this in a recent project. This is a bit of hacks, but it works. I tried calling cache_images () using after_validation and before_save in the model, but it cannot be created for some reason that I cannot determine, so I just call it from the controller. Hope this helps someone else for a while!

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: ''} 
+1
source share

All Articles