Using Rails with Flex to manage long running tasks
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)
One Comment, Comment or Ping
Reply to “Using Rails with Flex to manage long running tasks”