def sort(arr, crit, i)
final = []
arr = arr.sort_by {|a| a.send(crit[i])}
hash = arr.slice_into_groups(crit[i])
sorted_keys = hash.sort_by {|k, v| k}.gimme_firsts
if i < crit.size - 1
sorted_keys.each do |key|
final << sort(hash[key], crit, i+1)
end
else
final = arr
end
return final.flatten
end

class Array

def slice_into_groups(crit)
hash = {}
self.each do |a|
hash[a.send(crit)] = [] if hash[a.send(crit)].blank?
hash[a.send(crit)] << a
end
hash
end

def gimme_firsts
self.collect{|a| a.first}
end



And Tests for the Array code.



class ArrayTest < Test::Unit::TestCase
def test_slice_into_groups
objects = Model.find(:all)
assert_not_nil objects.size
old_size = objects.size

grouped_objects = objects.slice_into_groups('attribute')

sum = 0
grouped_objects.each do |k, v|
sum += v.size
v.each do |obj|
assert_equal k, obj.attribute
end
end
assert_equal old_size, sum
end

def test_gimme_firsts
assert_equal [1, 2, 3, 4, 5, 6, 7], [[1, 2, 3],[2, 2, 3],[3, 2, 3],[4, 2, 3],[5, 2, 3],[6, 2, 3],[7, 2, 3]].gimme_firsts
end
end



This is basically to keep tests in place for MySQL order bys ... which I know make absolutely no sense, but if there's a requirement and someone changes something I like a test to break to tell them I didn't put the order by in there for nothing.

The index passing down the call stack is fairly ugly but I need to find a better way.

"Open your eyes"
Metallica - Invisible Kid.