Powered By BlogNow - Get Your Free Blog

I have moved!

My new blog is at lindsaar.net

All the old content will stay here though. But check out the new blog

¿ 14/6/2007 - Ruby on Rails and BackgroundRB - multiple progress bars with ActiveScaffold

I spent a little while getting all this working... now I have, I thought I would share it for you.

I had a problem you see....

"I want to use ActiveScaffold, and allow me to upload files to the server, but these files are documents and then I want to be able to click "import" on each entry and with AJAX show EACH document individually being processed in the background with the percentage complete auto updating on the screen. This has to work for any number of files and each process needs to run independently of the others. Oh... also, if the person leaves the site and comes back later and clicks "import" again, it should show where it is up to, not start a new import."

Easy I hear you say :)

Well... this is how I did it:


  1. Have a working How to Install Ruby on Rails

  2. Get BackgroundRB

  3. Get Get ActiveScaffold

  4. Make a model:
  5. db/migrate/create_documents.rb
    class CreateDocuments < ActiveRecord::Migration
      def self.up
        create_table :documents do |t|
          t.column :title, :string
          t.column :created_at, :datetime
          t.column :updated_at, :datetime
          t.column :imported, :boolean
          t.column :job_key, :string
          t.column :progress, :integer
        end
      end
    
      def self.down
        drop_table :documents
      end
    end
    

  6. Turn on ActiveScaffold for the Document page and make an "import" button in Active Scaffold:
  7. app/controllers/documents_controller.rb
    class DocumentsController < ApplicationController
      active_scaffold :document do |config|
      config.action_links.add 'import', :label => 'Import',
                                            :type => :record,
                                            :method => 'put'
      end
    end
    

  8. Make a controller that will call the worker:
  9. app/controllers/documents_controller.rb
    def import
     if request.xhr?
       @document = Document.find(params[:id])
       unless (@document.imported == true)
         if ((@document.job_key == nil || ""))
           job_key = "#{Time.now.to_i}-DOC#{@document.id}"
           MiddleMan.new_worker(:class => :book_import_worker,
                                :args => {:document_id => @document.id,
                                :job_key => job_key},
                                :job_key => job_key)
           @document_id = @document.id
         end
       end
     end
    end 
    

  10. Make the worker you are going to call:
  11. lib/workers/book_import_worker.rb
    class BookImportWorker < BackgrounDRb::Rails
      def do_work(args)
        # Setup the job
        @document = Document.find_by_id(args[:document_id])
        @document.job_key = args[:job_key]
        @document.imported = false
        @document.save!
        @logger.info("Book import started on Document Job Key #{args[:job_key]}")
        # Initialize progress variable and total (set total to
        # how much you want progress to get to)
        @progress = 1
        @total = 200
        # Do some work
        while @document.progress < @total
          progress(1)
          # Do work.....
          sleep 2
        end
        # Clean up after yourself and set the document to being done
        @document.job_key = nil
        @document.imported = true
        @document.progress = 100
        @document.save!
        kill()
      end
      private
        # progress method to update the document
        def progress(p)
        @progress += p
        # Get the percentage complete
        @result = (@progress.to_f / @total) * 100
        # Sanitize it in case you are wrong on the "total estimate"
        @result = 1 if @result < 1
        @result = 100 if @result > 100
        # Print this to the log - you can watch it happening with
        # $RAILS_ROOT/tail -f log/backgroundrb.log
        @logger.info("Worker job_key: #{@document.job_key}, 
           total is: #{@total}, Progress is: #{@progress}, 
           Percentage done is #{@result}")
        # Save the progress
        @document.progress = @result.to_i
        @document.save!
      end
    end
    

  12. Make the importing page:
  13. app/views/documents/import.rhtml
    For some reason the editor is parsing my escaped HTML as real HTML!
    So I have bundled it all up here.

  14. Make the get status "ping" method in the controller:
  15. app/controllers/documents_controller.rb
    def ping
      @document = Document.find(params[:id])
      @percent = @document.progress
      @document_id = @document.id
      redirect_to :action => :index unless request.xhr?
    end
    

  16. Make the ping.rjs file
  17. app/views/documents/ping.rjs
    page.replace_html("progressbar#{@document_id}", 
                        :partial => "percent",
                        :object => @percent)
    
    # If the percentage is equal to or over 100... 
    # then replace it with a nice # finished partial. 
    # Note, there is some trickery here, you need to
    # ADD a SECOND element that has the ID 
    # "#{@document.id}indicator" and make it
    # hidden somewhere in the page 
    # otherwise the periodically_call_remote will
    # continue looking for it when you
    # close the active scaffold view.  
    #It is a pain in the butt. 
    if @percent >= 100
      page.replace_html("progressbar#{@document_id}", 
                         :partial => "finished", 
                         :object => @percent)
      page.insert_html(:after, "documents-active-scaffold",
                         :partial => "finished_hidden")
    end 
    

  18. Make the progress bar partial:
  19. app/views/documents/_percent.rhtml
    It is all in here.

  20. Make the finished bar partial:
  21. app/views/documents/_finished.rhtml
    Same file here.

  22. Make the hidden finished bar
  23. app/views/documents/_finished_hidden.rhtml
    Here it is again.

  24. Press PLAY!

  25. One day I'll do a screen cast... :) for now, you'll have to take my word for it that it looks really cool.

    There you go!

    blogLater

    Mikel
Post A Comment! :: Send to a Friend!

¿ 6/10/2007 - I don't see any upload fields in any of your document views

Posted by Anonymous
Mike, I'm trying to implement this, and everything renders okay, but I have no way (That I can see) of uploading a file.

Thanks,
Zack
Permanent Link

¿ 6/10/2007 - File uploads

Posted by mikel
The upload a file step is before this screen.

Basically, this screen tracks what the app is doing in the background. You can see the screen cast of what it looks like in a later blog of mine (I think the next day).

http://www.blognow.com.au/q/64767/Screen_cast_of_multiple_progress_bar_tracker.html

To do a file upload, if you are using Active Scaffold, the easiest way is to use the file column upload - which you can get as part of activescaffold.

The one I used in this app was the FileColumnBridge which you can find at:

http://wiki.activescaffold.com/wiki/show/FileColumnBridge

But this app had the uploads done before you click import. The importing was doing something with the files (in this case, converting them from Microsoft HTML Word format and importing them into a database).

Hope that points you in the right direction.

Mikel

Edited by mikel on 5/10/2007 at 10:17 AM
Permanent Link

¿ 18/2/2008 - Untitled Comment

Posted by mbecker
Hi mikel,

at first i want to thank for your introduction in BackgroundBR.

At first there are a few mistakes in your *.rhtml files: "@vms_document" is not specified.

But my question is:
If you want to upload something and you want to show a progress bar, so you need te complete size of the file and the size which is uploaded at every moment. With these two datas you can give out a percantage. But what you do is only to set up the percent every time up tp 1 when you calll "progress(1)" ?? In my case, you don't show how much of the 100% is already done. Or how do you give progress() the data ?
Permanent Link

¿ 19/2/2008 - Where is the upload button?

Posted by Mikel
@ mbecker

See my post above yours, this demonstration of backgroundrb and activescaffold is done with files that are already on the server, it is not an upload form or upload progress bar.

It is more of a processing system, the page takes some files and translates them into some other format, and this takes time. So we wanted an interactive way to show that.

Regards

Mikel

Permanent Link

About Me

AKA Raasdnil, this site is about web coding, hosting and all other matters that relate to this... especially Ruby on Rails!

Links