Merb was originally created by Ezra Zygmuntowicz to avoid some Rails upload issues.
This is one of the things that Merb was written for. Rails doesn‘t allow multiple concurrent file uploads at once without blocking an entire rails backend for each file upload. Merb allows multiple file uploads at once.
I’ve built ‘multiple’ file uploaders for Rails sites but they always involved some slight of hand, the files appeared to be uploading all at once but they where actually queued up by Flex then handled one by one by the app (which also had the unhappy side effect of blocking any other requests to that process). I’ve been wanting to try out Adobe AIR’s file system drag and drop for a while so this is a two-fer example. You’ll need the beta version of Flex Builder 3 or the Flex 3 SDK beta if you don’t mind getting down with the command line.
In a hurry? Here’s one I made earlier (flex source in ‘dist/app/fx‘).
If you haven’t before install Merb
$ sudo gem install merb
Then create a new Merb app
$ merb -g merb_air_upload
and dive on in
$ cd merb_air_upload
We’ll need two folders not in a Merb skeleton, one for Flex, and one for our uploads
$ mkdir dist/app/fx
$ mkdir dist/public/uploads
Create a local database called ‘merb_air_upload’ and edit dist/conf/merb_init.rb so that the database definition matches your setup
ActiveRecord::Base.establish_connection(
:adapter => ‘mysql‘,
:username => ‘root‘,
:password => ‘‘,
:database => ‘merb_air_upload‘
)
Our model will be called ‘UserFile’ as ‘Upload’ and ‘File’ are reserved words, create a migration
$ ./script/new_migration CreateUserFiles
and edit it (dist/schema/migrations/002_create_user_files.rb) to look like so
class CreateUserFiles < ActiveRecord::Migration
def self.up
create_table :user_files do |t|
t.column :filename, :string, :null => false
end
end
def self.down
drop_table :user_files
end
end
rake your db
$ rake db:migrate
Then create a UserFile model (dist/app/models/user_file.rb)
class UserFile < ActiveRecord::Base
end
and an upload controller (dist/controllers/upload.rb)
class Upload < Application
def index
puts params.inspect
@upload = UserFile.new
@upload.filename = params[:Filename]
if @upload.save
dist_root = Merb::Server.config[:dist_root]
FileUtils.mkdir dist_root + “/public/uploads/#{@upload.id}“
destination = dist_root + “/public/uploads/#{@upload.id}/#{params[:Filename]}“
FileUtils.mv params[:Filedata][:tempfile].path, destination
else
false
end
end
end
That’s it for the Merb side of thing on to our AIR app, fire up Flex Builder and create a new AIR project

I like to keep my flex files in my Rails/Merb app directory

The AIR app is three files; the main MXML file (dist/app/fx/merb_air_upload.mxml), a code behind class (dist/app/fx/com/vixiom/merb_air_upload/App.as), and an upload progress component that gets repeated for each file (dist/app/fx/com/vixiom/merb_air_upload/UploadProgressComponent.mxml). Here’s the main MXML file:
<?xml version=“1.0“ encoding=“utf-8“?>
<app:App
xmlns:mx=“http://www.adobe.com/2006/mxml“
xmlns:app=“com.vixiom.merb_air_upload.*“
layout=“vertical“
width=“300“ height=“375“
backgroundGradientAlphas=“[1.0, 1.0]“
backgroundGradientColors=“[#F4F4F4, #E0E0E0]“
paddingBottom=“10“ paddingLeft=“10“ paddingRight=“10“ paddingTop=“10“
verticalScrollPolicy=“off“ horizontalScrollPolicy=“off“>
<mx:VBox id=“files_vb”
width=“100%” height=“300″
backgroundColor=“#FFFFFF”
horizontalScrollPolicy=“off”
paddingBottom=“5″ paddingLeft=“5″ paddingRight=“5″ paddingTop=“5″ borderColor=“#ABABAB” borderStyle=“inset”>
</mx:VBox>
<mx:Button id=“upload_btn” label=“Upload Files”/>
</app:App>
It’s hooked up to it’s code behind ‘App.as’ class by the xmlns tag (xmlns:app=“com.vixiom.merb_air_upload.*“) App.as extends WindowedApplication which is the base of all AIR apps:
package com.vixiom.merb_air_upload
{
import com.vixiom.merb_air_upload.UploadProgressComponent
import mx.core.WindowedApplication;
import mx.containers.VBox;
import mx.controls.Button;
import mx.events.FlexEvent;
import flash.events.*;
import flash.desktop.*;
import flash.filesystem.File;
import flash.net.*;
public class App extends WindowedApplication
{
private var filesToUpload :Array
private var UploadProgressComponents :Array;
public var files_vb:VBox;
public var upload_btn:Button;
private var uploadURL:URLRequest;
public function App() :void
{
addEventListener( FlexEvent.CREATION_COMPLETE, creationCompleteHandler );
}
private function creationCompleteHandler( event:FlexEvent ) :void
{
addEventListener( NativeDragEvent.NATIVE_DRAG_ENTER, onDragEnter );
addEventListener( NativeDragEvent.NATIVE_DRAG_DROP, onDragDrop );
upload_btn.enabled = false;
upload_btn.addEventListener( MouseEvent.CLICK, upload );
uploadURL = new URLRequest();
uploadURL.url = “http://localhost:4000/upload“;
uploadURL.method=URLRequestMethod.POST;
filesToUpload = new Array();
UploadProgressComponents = new Array();
}
private function onDragEnter( event:NativeDragEvent ) :void
{
DragManager.acceptDragDrop(this);
}
private function onDragDrop( event:NativeDragEvent ) :void
{
DragManager.dropAction = DragActions.COPY;
var files:Array = event.transferable.dataForFormat( TransferableFormats.FILE_LIST_FORMAT ) as Array;
for each (var f:File in files)
{
addFile( FileReference( f ) );
}
upload_btn.enabled = true;
}
private function addFile( f:FileReference ) :void
{
filesToUpload.push( f );
var upv:UploadProgressComponent = new UploadProgressComponent();
UploadProgressComponents.push( upv );
files_vb.addChild( upv );
upv.file_lb.text = f.name;
upv.pb.source = f;
f.addEventListener( Event.COMPLETE, completeHandler );
f.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler );
}
private function completeHandler( e:Event ) :void
{
var f:FileReference = FileReference(e.target);
for( var i:uint; i < filesToUpload.length; i++ )
{
if( f.name == filesToUpload[i].name )
{
files_vb.removeChild( UploadProgressComponents[i] );
filesToUpload.splice(i, 1);
UploadProgressComponents.splice(i, 1);
}
}
}
private function ioErrorHandler( event:IOErrorEvent ) :void
{
trace(“ioErrorHandler: “ + event);
}
private function upload( e:MouseEvent ) :void
{
for each (var f:File in filesToUpload)
{
f.upload( uploadURL );
}
}
}
}
The last file is the upload progress component, it’s progress bar listens for events from each file (upv.pb.source = f; above in the addFile method)
<?xml version=“1.0“ encoding=“utf-8“?>
<mx:VBox
xmlns:mx=“http://www.adobe.com/2006/mxml”
paddingBottom=“5″ paddingLeft=“5″ paddingRight=“5″ paddingTop=“5″
borderColor=“#BABABA” borderStyle=“solid” borderThickness=“2″
width=“100%”
backgroundColor=“#F7F7F7″>
<mx:Label id=“file_lb” text=“Label” fontWeight=“bold” color=“#000000″/>
<mx:ProgressBar id=“pb” labelPlacement=“right” label=“” width=“100%”/>
</mx:VBox>
That’s it! test your AIR app by dragging some files from the file system, once you drop them the upload progress components show a visual representation of the files, click ‘upload files’ and the files are upload all at once (for real real not for play play this time).

drag!

drop!

upload!