Upload Progress Bar with Hobo and Paperclip

Originally written by ignacio on 2011-06-12.

This recipe has been tested with Hobo 1.0.1 and Rails 2.3.8.

It uses Phusion Passenger, Ajax and a iframe hack to get a nice ajax progress bar for the uploads.

Before we start, you can take a look at the demo video I recorded and the complete demo in .tar.gz.

Step One: Create two tables and install paperclip

We create the application and two tables: clients and attachments

hobo demo_progress_bar
cd demo_progress_bar
script/generate hobo_model_resource client name:string
script/generate hobo_model_resource attachment

Every client has_many attachments:

# app/models/client.rb
has_many :attachments

# app/models/attachment.rb
belongs_to :client

Create and run the migrations:

script/generate hobo_migration

Now we add the Hobo magic to connect the clients and the attachments:

# app/viewhints/client_hints.rb:
children :attachments

# app/controllers/attachments_controller.rb:
  auto_actions :write_only
  auto_actions_for :client, :create

Next we install Paperclip and Paperclip with Hobo.

script/plugin install git://github.com/thoughtbot/paperclip.git
script/plugin install git://github.com/tablatom/paperclip_with_hobo.git

I found a bug between the latest paperclip and paperclip_with_hobo, so you can try this slightly older paperclip revision (untar it in vendor/plugins): http://ihuerta.net/wp-content/uploads/2011/06/Paperclip.tar.gz

cd vendor/plugins
wget http://ihuerta.net/wp-content/uploads/2011/06/Paperclip.tar.gz
tar xzfv Paperclip.tar.gz

Now we prepare the model, the views and the migrations

  has_attached_file :file, 
      :whiny => false, 
      :path => "#{RAILS_ROOT}/files/:id.:extension"
  validates_attachment_size :file, :less_than => 5.megabytes
  validates_attachment_presence :file

  def name
  def size
    if (file_file_size / 1024) < 1024
      (file_file_size / 1024).to_s + ' KB'
      (file_file_size / 1048576).to_s + ' MB'


  <form: enctype="multipart/form-data">
    <field-list: fields="file"/>


<!-- Paperclip support -->
<include src="paperclip" plugin="paperclip_with_hobo"/>
<def tag="input" for="Paperclip::Attachment"> 
  <%= file_field_tag param_name_for_this, attributes %> 

<!-- Card for every attachment -->
<def tag="card" for="Attachment">
  <card class="attachment" param="default" merge>
    <header: param>
      <h4 param="heading"><a href="/attachments/download/#{this.id}"><name/></a></h4>
      <div param="actions">
        <delete-button label="X" param/>
      <p>Size: <this.size/></p>
      <p>Date: <this.created-at/></p>


  def download
    attachment = Attachment.find(params[:id])
    send_file 'files/' + attachment.name
script/generate hobo_migration

Step 2: Start tracking the uploads with Phusion Passenger

In order to track the progress of uploads, we need the server to give us that information. “Apache Upload Progress Module” is a nice extension that solves our problem.

Download https://github.com/drogus/apache-upload-progress-module

Extract it

Install it

sudo apxs2 -c -i -a mod_upload_progress.c

Prepare the virtualhost to track uploads:

<VirtualHost demo_progress_bar.localhost:80>
  ServerName demo_progress_bar.localhost
  DocumentRoot '/home/ignacio/Trabajos/2_Proyectillos/Hobo/demo_progress_bar/public'
  RailsEnv development
  <Directory '/home/ignacio/Trabajos/2_Proyectillos/Hobo/demo_progress_bar/public'>
     AllowOverride all
     Options -MultiViews

   <Location />
     # enable tracking uploads in /
     TrackUploads On

   <Location /progress>
     # enable upload progress reports in /progress
     ReportUploads On


And a nice tip if you are working with Ubuntu. While you are testing in localhost, it’s very nice to simulate a slow upload speed so you can actually see the bar moving. I use this iprelay command and work through http://demo_progress_bar.localhost:8002

iprelay -b350000 8002:demo_progress_bar.localhost:80

Before we start with the upload bar, let’s check if the tracking is working. First we add a test UUID to the file upload request:


  # Random UUID for the upload
  uuid = (0..29).to_a.map {|x| rand(10)}.to_s
  <form: enctype="multipart/form-data"
    <field-list: fields="file"/>
      <a href="/progress/?X-Progress-ID=#{uuid}">Testing the upload URL</a>

When you reload the client page, you will see a link that gives you a JSON answer, something like:

{ "state" : "starting", "uuid" : "659102713689307356231103628731" }

This means the download hasn’t yet started. If you are uploading the file, the JSON should be:

{ "state" : "uploading", "received" : 47104, "size" : 173966, "speed" : 47104, 
"started_at": 1307856054, "uuid" : "659102713689307356231103628731" }

With this info we are going to create the progress bar :)

Step 3: Build the progress bar

Now let’s get to the fun part. First we need a JS function to take care of the progress-bar. Basically it gets the JSON info every couple of seconds, and based on the answer it decided what to do:


function hoboprogress(uuid){
  // Make the progress bar appear
  $('progress-bar').setStyle('display: block;');
  // Reload the Progress Bar every 2 seconds
  new PeriodicalExecuter(
      new Ajax.Request("/progress",{
        method: 'get',
        parameters: 'X-Progress-ID='+uuid,
        onSuccess: function(xhr){
          /* When we get the upload info in JSON, we evaluate it */
          var upload = xhr.responseText.evalJSON();
          if(upload.state == 'uploading'){
            /* Calculate the percentage */
            upload.percent = Math.floor((upload.received / upload.size) * 100);
            $('progress-bar').setStyle({width: upload.percent + "%"});
            $('progress-bar').update(upload.percent + "%" + upload.speed);
          /* Once we are in the 100%, we trigger some stuff */
          if(upload.state == 'done' || upload.percent == 100){
            // Stop the PeriodicalExecuter
            // Change the message
            // Update the file list (we wait a second so the server writes the new file to the DB)
            Hobo.ajaxRequest.delay(0.8,'/clients/show/3', ['collection'],{
              onSuccess: function(response){
                // Hide the progress bar once the update is complete
            // Empty the file input box
            // This is a bit complicated to do cross browser
            // Apparently, replacing the HTML works best
            $('file-input').update("<input type='file' name='attachment[file]' id='attachment_file' class='file-tag paperclip--attachment attachment-file'>");

Ok, too much JS, I know. But the result is worth it! Now let’s add the progress-bar div to the show.dryml, plus an iframe for the upload to work:


  # Random UUID for the upload
  uuid = (0..29).to_a.map {|x| rand(10)}.to_s
  <form: enctype="multipart/form-data"
    <field-list: fields="file">
      <file-view: id="file-input"/>
    <submit: onclick="hoboprogress('#{uuid}', '#{@client.id}');"/>
      <!-- Progress Bar -->
      <div id="progress-bar" style="background-color:green; color:white; font-size:20px; padding:10px; display:none;">Starting upload</div>
      <!-- Iframe for the Ajax upload -->
      <iframe id="jobdummy" name="jobdummy" 
  <collection: replace>
    <collection:attachments part="collection"/>

Last step: make the controller answer to Ajax requests:

# app/controllers/clients_controller.rb
  def show
    hobo_show do
      hobo_ajax_response if request.xhr?

Edit this page