Using Rails with Flex to manage long running tasks

UPDATE Jul 2008 - The API for BackgroundRB has changed and docs improved significantly since this blog post. Read the latest API calls over here http://backgroundrb.rubyforge.org/rails/. Don’t reference the Rails code below if using latest BackgroundRB

As soon as you start doing anything with photos sooner or later someone says “It would be really nice to upload these in a zip file”, which then leads into a whole rabbit warren of issues, one of which you will inevitably come across is how to deal with a long running task on the server, in terms of a) getting it to actually complete and b) telling the user what’s going on. There are various hacks for doing these, like running a shell script and writing out tmp files all over the place. Just plain ugly.

Enter Backgroundrb, probably one of the greatest little inventions for Rails. It lets you kick off a background process, outside of the main rails app process, with the option to interact with the rails models or not. The beauty of this plugin is a little thing called MiddleMan, as the name implies it lets you interact with your background processes (Workers) from your Rails app.

So, this gives us the ability, from your Flex app to invoke a Worker on the server side, then monitor that Worker until it’s finished, and/or kill off the worker if need be (for slacking on the job?).

What do we need.

1. First off install BackgroundRB from here.

2. Next we need to create our worker in your Rails app dir ’script/generate worker ResizerWorker . Here’s a simple chunk of worker code.

class ResizerWorker < BackgrounDRb::Rails
  def do_work(args)
    @total_images = 50
    @current_image = 1
    50.times do
      @current_image += 1
      sleep(1)
    end
  end
 
  def get_progress
    @current_image < 50 ? status = 'In progress' :  status = 'Finished'
    info = { :status => status, :current_image => @current_image, :total_images => @total_images}
    return info
  end
end

As you can see we have a do_work method which is generated by default, this is called whenever the worker is instantiated so it kicks off our work to be done. Then I added a get_progress method to return a hash of the worker’s current status.

3. Great so now we need to kick it off, there’s some options to do this automatically at timed intervals, but I’m not going to go into that here, all we’re doing is getting our rails app, by a web request to start/stop and monitor the progress (the web request is then called from Flex) . So we create a watcher controller, with actions ’start_task’, ’status’ and ’stop_task’ as below which is all pretty self explanatory:

class WatcherController < ApplicationController
 
  def start_task
    session[:worker_key] = MiddleMan.new_worker(:class => :resizer_worker)
    info = {:message => 'job_started', :key => session[:worker_key]}
    render :xml => info.to_xml(:dasherize => false)
  end
 
  def status
    @worker = MiddleMan.get_worker(session[:worker_key])
    if @worker
      info = @worker.get_progress
      if info[:status] == 'Finished'
        MiddleMan.kill_worker(session[:worker_key])
      end
    else
      info = { :status => 'Finished' }
    end
    render :xml => info.to_xml(:dasherize => false)
  end
 
  def stop_task
    @worker = MiddleMan.kill_worker(session[:worker_key])
    info = { :status => 'Task Stopped' }
    render :xml => info.to_xml(:dasherize => false)
  end
 
end

Gotcha! I found out the method names in the Documentation, on MiddleMan, were wrong compared to the version I had installed. I needed to use .new_worker, .get_worker and .kill_worker.

4. Fantastic, so go into console, do “rake backgroundrb:start” in your rails directory, then fire up a rails server. You should now be able to hit http://localhost:3000/watcher/start_task, http://localhost:3000/watcher/status, http://localhost:3000/watcher/stop_task and see the appropriate results.

5. Now you can fire HTTPService requests on the Flex side and consume the results as required. For example : (for the sake of berevity I haven’t put in any timer function to recheck the status, just a manual click event)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" verticalAlign="middle" horizontalAlign="center">
<mx:Script>
	<![CDATA[
		import mx.rpc.events.ResultEvent;
		import mx.controls.Alert;
 
		private function onSvcWatchTaskResult(event:ResultEvent):void
		{
			trace(event.result.valueOf());
			var info:XML = XML(event.result);
			pbProgress.setProgress( info.current_image, info.total_images );
		}
 
		private function onSvcStartTaskResult(event:ResultEvent):void
		{
			Alert.show('Task Started');
		}
	]]>
</mx:Script>
<mx:HTTPService id="svcWatchTask" url="http://localhost:3000/watcher/status" useProxy="false" method="GET" resultFormat="e4x" result="onSvcWatchTaskResult(event)"/>
<mx:HTTPService id="svcStartTask" url="http://localhost:3000/watcher/start_task" useProxy="false" method="GET" resultFormat="e4x" result="onSvcStartTaskResult(event)"/>
<mx:HTTPService id="svcCancelTask" url="http://localhost:3000/watcher/stop_task" useProxy="false" method="GET" resultFormat="e4x"/>
 
	<mx:Panel label="Task Watcher" width="600" height="600" x="156.5" y="10">
		<mx:ProgressBar id="pbProgress" mode="manual" minimum="0" maximum="100"/>
		<mx:ControlBar>
			<mx:Button label="Start task" click="svcStartTask.send()"/>
			<mx:Button label="Check task" click="svcWatchTask.send()" />
			<mx:Button label="Cancel task" click="svcCancelTask.send()"/>
		</mx:ControlBar>
	</mx:Panel>
</mx:Application>

And there you go. A few seemingly simple lines of code has knocked off one of the age old problems of dealing with long running tasks. I heart Rails (and Flex).

(As always with code examples, don’t copy and paste, rewrite it yourself. You’ll understand it better and won’t copy in any errors I’ve made whilst cutting it down for blogging)

3 Comments, Comment or Ping

  1. Matt

    Do not use this tutorial, it is disjointed and incorrect. Silly mistakes exist in it such as “script/generate/worker ResizerWorker” which should really read “script/generate worker ResizerWorker”.

    On top of this vital information on how to invoke methods is completely missing.

    If you want real information check out the official API over at http://backgroundrb.rubyforge.org/rails/index.html

  2. admin

    Yes a typo exists for the script/generate command which any railer will figure out. Anyone will realise that API’s change for projects over time (this was written almost a year ago - forgotten I’d even written it), so relying on old blog posts is risky business.

    At the time the API calls were correct and this post was written up because the docs weren’t clear/correct for the available download.

    But thanks for taking the time to point it out. Updating post accordingly.

Reply to “Using Rails with Flex to manage long running tasks”

About

Rowan is a Product Development Manager, specialising in architecting, developing and putting web applications into production - in particular Ruby on Rails based apps. He lives in Toronto, Canada but speaks in a funny accent as he's originally from New Zealand. He's been working in the software and web business for over a decade. This blog covers Web Application development and deployment in the real world, dealing with topics from business fundamentals to Ruby on Rails, Merb, PHP, Flex, MySQL, Apache and more.

Read more ...

 

 

View Rowan Hick's profile on LinkedIn

 

Subscribe to my RSS feed