A collection of computer systems and programming tips that you may find useful.
Brought to you by Craic Computing LLC, a bioinformatics consulting company.

Wednesday, November 26, 2008

Rails - Drag and Drop Sorting in a Table

Through use of the Prototype and Script.aculo.us libraries, Rails makes it easy to set up drag and drop capability in your pages.

But all the examples I've seen use Lists of elements and I typically use Tables to display sets of data. I was having a hard time getting drag and drop sorting to work but I got there.

My problem was that I was putting my 'container id' into the <table> tag. That doesn't work. You have to have a <tbody> tag pair inside there - which I simply have never bothered with. Put the 'container id' in there and put the 'element id' in the <tr> tag - not the <td> tag.

In this dummy application you have carts and carts have multiple items. The order of each item in the cart is given by the item 'position' field.
class Cart < ActiveRecord::Base
has_many :items, :order => :position

class Item < ActiveRecord::Base
belongs_to :cart
acts_as_list :column => :position, :scope => :cart

This would go into your cart show page:
<tbody id="cart_div">
<% @cart.items.each do |item| %>
<tr id="item_<%= item.id %>">
<td><%= item.position %></td>
<td><%= item.name %></td>
<% end %>

Then elsewhere in that page you need to add this block that sets up the JavaScript magic. Note that you need to specify the tag that will get dragged and dropped. The default is 'li' but here we want 'tr'.
<%= sortable_element 'cart_div',
:url => { :action => 'sort', :id => @cart },
:complete => visual_effect(:highlight, 'cart_div'),
:tag => 'tr'

You also need to create a 'sort' action in your carts controller that looks something like this:
  def sort
@cart = Cart.find(params[:id])
@cart.samples.each do |cart|
item.position = params['cart_div'].index(item.id.to_s) + 1
render :nothing => true

Note that you don't need to do anything with your routes.rb file, even though 'sort' is not a standard action. I assume that is because it is using a GET and that just gets handled?

Finally, you need to include the javascript libraries in your page layout, but you knew that already.
<%= javascript_include_tag :defaults %>

Monday, November 24, 2008

Installing Rails 2.2 on Mac OS X - MySQL problem

Rails 2.2 is out and you want to install it - but you may run into this issue on Mac OS X.

1: Make sure you have upgraded to rubygems 1.3.1
$ sudo gem update --system

If that barfs try this which does the same thing a different way:
$ sudo gem install rubygems-update
$ sudo update_rubygems

2: Install Rails
This should install just fine
$ sudo gem install rails

3: One important change in 2.2 is that the Mysql database driver is no longer bundled and you have to install yourself. But the obvious command may well fail like this:

$ sudo gem install mysql
Building native extensions. This could take a while...
ERROR: Error installing mysql:
ERROR: Failed to build gem native extension.

The issue is that the gem needs more information about your MySQL installation, so do this instead:

$ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
Building native extensions. This could take a while...
Successfully installed mysql-2.7
1 gem installed

4: That looks good but you may not be out of the woods yet... I got this when I tried a rake db:migrate

$ rake db:migrate
(in /Users/jones/Documents/myapp)
!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.
rake aborted!
dlsym(0x1c71570, Init_mysql): symbol not found - /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7/lib/mysql.bundle

After some poking around I found reference to the version of mysql that is installed.

I installed mine as the packaged disk image from mysql.com. For Mac OS X 10.5 you have the choice of these two:

Both work fine on my MacBook but the mysql gem wants the '_x86' version, not the '_x86_64'

Look in /usr/local to see which you have symlinked in:

$ ls -l /usr/local
lrwxr-xr-x 1 root wheel 24 Nov 24 09:35 mysql -> mysql-5.0.67-osx10.5-x86

Install the correct version, reinstall the gem just to be safe, and try your rake again - it should be fine!


If you do have the same issue and install the non _64 version on top of the _64 one then mysql will NOT copy over your data files. To fix this:

1: Shut down MySQL
2: In the new (non _64) version
$ sudo mv data data.bak
$ sudo cp -pr ../mysql-5.0.67-osx10.5-x86_64/data .

3: Start up MySQL

Wednesday, November 19, 2008

Wrapping Scripts with Platypus on Mac OS X

Platypus is a Mac OS X tool, written by Sveinbjorn Thordarson, that lets you create Mac OS X applications that wrap up command line scripts, create installers, etc. It is a great way to package Ruby, Perl, etc. scripts for a broader user audience.

Platypus is easy to figure out if you just have a single script. But I often times develop data analysis pipelines of various sorts where I have one master Ruby script calling a bunch of others.

Wrapping the whole thing up with Platypus can work really well but to do that you need to understand how the tool works.

At first glance you might think that it just calls the target script in its original location, but in fact it copies the script into the folder that embodies the Mac OS X application. So if I have a primary script calling a secondary one then I need to either refer to it by an absolute path, which is not a good idea for portability, or I need to include the secondary script in the Mac application.

Here is an example that shows how you handle this - I'll assume that you have tried Platypus with some simple scripts already.

The primary script (hello.rb) is what the Platypus generated application will execute. This in turn will execute world.rb and echo its output.


puts "Hello ..."
world = File.join(File.dirname(__FILE__), 'world.rb')
puts `#{world}`


puts "... World"

First of all, specify the path to your Ruby (or whatever) interpreter explicitly. The convention in the Ruby world is to use '#!/usr/bin/env ruby' but doesn't work directly with Platypus. I'm sure you can mess with some of the Platypus options to make it work but I haven't bothered.

The third line in hello.rb specifies where to find the script 'world.rb', namely in the same directory as hello.rb. For Perl coders, this is the same as using FindBin.

Package hello.rb with Platypus, sending output to a Text window and keeping the window open after the script completes, then run the script. You should get something like this:

/Users/jones/Documents/Platypus/hello_world.app/Contents/Resources/script:7: command not found: /Users/jones/Documents/Platypus/hello_world.app/Contents/Resources/world.rb
Hello ...

It has found and run hello.rb but it can't find world.rb. The error message tells us that the script it has run is actually called 'script', not hello.rb, which seems odd.

Platypus copies your script into a new Mac application, which as you should know, is really a directory. 'cd' to the application directory (hello_world.app in my case), then into Contents -> Resources.

Look at the file 'script' and you'll see that is actually 'hello.rb'

To get the result you want you have to add 'world.rb' to the application as a 'Resource' and you do this in the 'Advanced Options' panel in Platypus. Just add the secondary file to this panel, rebuild the application and re-run it.

Hello ...
... World

With more complex applications you can add additional scripts and data files. Test things out by running them from the command line in the platypus generated application directory.

If you want to get around this you can specify an absolute path to the secondary script, or create a symlink to it from within the application directory. But down this path lies madness. A better alternative would be to use an environment variable to show where something lies, such as the PATH variable or something custom. Platypus will let you pass that into the app.

I'm very impressed with Platypus, compared to earlier attempts at doing this, like DropScript. When I get the chance I want to try it in combination with CocoaDialog which allows you to create simple GUIs in your scripts.

Archive of Tips