Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi step form with image uploader

I want to build 3 step user registration with avatar uploading on 2nd step. So i follow Ryan Bates's guide http://railscasts.com/episodes/217-multistep-forms . I'm using CarrierWave gem to handle uploads. But it seems like i can't store uploaded file info in user session (i'm getting can't dump File error). I use following technique in controller

if params[:user][:img_path]
  @uploader = FirmImgUploader.new
  @uploader.store!(params[:user][:img_path])
  session[:img] = @uploader
  params[:user].delete(:img_path)
end

It actually helps. But when i upload forbidden file type everything's crashes on this line

@uploader.store!(params[:user][:img_path])

with this error

CarrierWave::IntegrityError in UsersController#create
You are not allowed to upload "docx" files, allowed types: ["jpg", "jpeg", "gif", "png"]

instead of normal form validation error.

How can i solve this problem ? Thanks !

like image 910
AlphaB Avatar asked Apr 27 '12 17:04

AlphaB


2 Answers

Actually I solved my problem. Here's working code for multistep forms with file uploading using carrierwave

if params[:user][:img_path]
  @uploaded = params[:user][:img_path]
  params[:user].delete(:img_path)
end
session[:user_data].deep_merge!(params[:user]) if params[:user]
@user = User.new(session[:user_data])    

if @uploaded
  # here how validation will work
  @user.img_path = @uploaded
end
@user.current_stage = session[:register_stage]
if @user.valid?
  if @user.last_stage?
    @user.img_path  = session[:img] if @user.last_stage?
    @user.save
  else  
    @user.next_stage
  end
  # now we can store carrierwave object in session
  session[:img] = @user.img_path
  session[:register_stage] = @user.current_stage
end
like image 63
AlphaB Avatar answered Oct 26 '22 12:10

AlphaB


This may be a little late for the OP, but hopefully this helps someone. I needed to store an uploaded image in the user's session (again for a multi-step form), and I too started with Ryan's Railscast #217, but the app quickly evolved beyond that. Note that my environment was Rails 4 on Ruby 2, using Carrierwave and MiniMagick, as well as activerecord-session_store, which I'll explain below.

I believe the problem that both OP and I had was that we were trying to add all of the POST params to the user's session, but with a file upload, one of the params was an actual UploadedFile object, which is way to big for that. The approach described below is another solution to that problem.

Disclaimer: As is widely noted, it's not ideal to store complex objects in a user's session, better to store record identifiers or other identifier data (e.g. an image's path) and look up that data when it's needed. Two major reasons for this keeping the session and model/database data in sync (a non-trivial task), and the default Rails session store (using cookies) is limited to 4kb.

My Model (submission.rb):

class Submission < ActiveRecord::Base
    mount_uploader :image_original, ImageUploader
    # ...
end

Controller (submissions_controller.rb):

def create
  # If the submission POST contains an image, set it as an instance variable, 
  # because we're going to remove it from the params
  if params[:submission] && params[:submission][:image_original] && !params[:submission][:image_original].is_a?(String)
    # Store the UploadedFile object as an instance variable
    @image = params[:submission][:image_original]
    # Remove the uploaded object from the submission POST params, since we 
    # don't want to merge the whole object into the user's session
    params[:submission].delete(:image_original)
  end

  # Merge existing session with POST params
  session[:submission_params].deep_merge!(params[:submission]) if params[:submission]

  # Instantiate model from newly merged session/params
  @submission = Submission.new(session[:submission_params])
  # Increment the current step in the session form
  @submission.current_step = session[:submission_step]

  # ... other steps in the form

  # After deep_merge, bring back the image
  if @image
    # This adds the image back to the Carrierwave mounted uploader (which 
    # re-runs any processing/versions specified in the uploader class):
    @submission.image_original = @image
    # The mounted uploader now has the image stored in the Carrierwave cache, 
    # and provides us with the cache identifier, which is what we will save 
    # in our session:
    session[:submission_params][:image_original] = @submission.image_original_cache
    session[:image_processed_cached] = @submission.image_original.url(:image_processed)
  end

  # ... other steps in the form

  # If we're on the last step of the form, fetch the image and save the model
  if @submission.last_step?
    # Re-populate the Carrierwave uploader's cache with the cache identifier 
    # saved in the session
    @submission.image_original_cache = session[:submission_params][:image_original]
    # Save the model
    @submission.save
    # ... render/redirect_to ...
  end
end

My uploader file was mostly stock with some custom processing.

Note: to beef up sessions, I'm using activerecord-session_store, which is a gem that was extracted from the Rails core in v4 that provides a database-backed session store (thus increasing the 4kb session limit). Follow the documentation for installation instructions, but in my case it was pretty quick and painless to set it and forget it. Note for high-traffic users: the leftover session records don't seem to be purged by the gem, so if you get enough traffic this table could potentially balloon to untold numbers of rows.

like image 39
Astockwell Avatar answered Oct 26 '22 11:10

Astockwell