Simplest AJAX upload with Rails Carrierwave and jQuery

The time has finally come for a follow-up to my post from a couple years ago on using jQuery, attachment_fu, and Rails 2.3 to upload an asset to my blog. I wanted to share the updated version of my attempt to determine the absolute minimal code necessary to implement AJAX uploads on Rails 3 with Carrierwave.  As was the case a few years ago, the Google results still tend to suck when searching for a simple means to accomplish an AJAX upload with Rails — the most popular result I could find this evening was a Stackoverflow post that detailed 9 (ick) steps, including adding a gem to the project and creating new middleware.  No thanks!

The Javascript from my previous example is essentially unchanged.  It uses jQuery and the jQuery-form plugin. The main challenge in getting a AJAX uploading working is that form_for :remote doesn’t understand multipart form submission, so it’s not going to send the file data Rails seeks back with the AJAX request. That’s where the jQuery form plugin comes into play. Following is the Rails code that goes in your html.erb. Remember that in my case I am creating an image that will be associated with a model “BlogPost” that resides in the BlogPostsController. Adapt for your models/controllers accordingly:

<%= form_for(:image_form, :url => {:controller => :blog_posts, :action => :create_asset}, :remote => true, :html => {:method => :post, :id => 'upload_form', :multipart => true}) do |f| %>
 Upload a file: <%= f.file_field :uploaded_data %>
<% end %>

Here’s the associated Javascript:

$('#upload_form input').change(function(){
 $(this).closest('form').ajaxSubmit({
  beforeSubmit: function(a,f,o) {
   o.dataType = 'json';
  },
  complete: function(XMLHttpRequest, textStatus) {
   // XMLHttpRequest.responseText will contain the URL of the uploaded image.
   // Put it in an image element you create, or do with it what you will.
   // For example, if you have an image elemtn with id "my_image", then
   //  $('#my_image').attr('src', XMLHttpRequest.responseText);
   // Will set that image tag to display the uploaded image.
  },
 });
});

Now, chances are you’re uploading this asset from a #new action, which means that the resource (here, the BlogPost) that will be associated with the image has yet to be created. That means we’re going to need a model that we can stick the AJAX-created image in until such time that the main resource has been created. We can do this if we create a migration for a new BlogImage model like so:

def self.up
  create_table :blog_images do |t|
    t.string :image
  end
  add_column :blog_posts, :blog_image_id, :integer # once created, we'll want to reference the BlogImage we created beforehand via AJAX
end

The corresponding BlogImage model would then be:

class BlogImage < ActiveRecord::Base
  mount_uploader :image, BlogImageUploader
end

Of course, if your resource already exists at the time the AJAX upload will happen, then you're on easy street. In that case, you don't have to create a separate model like BlogImage, you can just add a column to your main resource (BlogPost) and mount the uploader directly to BlogPost. In either case, the BlogImageUploader class would be setup with whatever options you want, per the Carrierwave documentation.

Continuing under the assumption that you will separate your model from the main resource (in this case, the BlogImage, which is separate from the BlogPost), we can create this image before the BlogPost exists, and stash the BlogPost id however you see fit. Thus, your controller's #create_asset method will look like:

def create_asset
  blog_image = BlogImage.new
  blog_image.image = params[:image_form][:uploaded_data]
  blog_image.save!

  # TODO: store blog_image.id in session OR pass ID back to form for storage in a hidden field
  # OR if your main resource already exists, mount the uploader to it directly and go sip on a 
  # pina colada instead of worrying about this
 
  render :text => blog_image.image.url
end

And that's it. No new gems, plus low-fat Javascript and controller additions.

Bonus section: How to embed this AJAX upload form in the form for its parent resource

One of the more common questions from my last post was how to display this AJAX image upload amongst the form for another resource. There are many ways to accomplish this (see comments from last post if you've got time to kill), but in keeping with the spirit of simplicity in this post, one fast hack I've used:

  1. After all the form fields for the main resource, close the form without a submit button
  2. Insert the AJAX form
  3. Add a link below the AJAX form that is styled to look like a button. Have this link call Javascript to submit your main form

Not going to win any beauty contests, but easy to setup and gets the job done.

One Reply to “Simplest AJAX upload with Rails Carrierwave and jQuery”

Leave a Reply to Bill Cancel reply

Your email address will not be published. Required fields are marked *