I want to do the following in Rails. Anyone know if it’s possible?

I have a a sortable list of pictures with a bunch of embedded form elements (thumbnail and full-size URLs, remote IDs, etc.). I’d like to set this up with array-style form elements so that the order of the form elements in the DOM would itself tell the server the order of the pictures, without my having to constantly recalculate the sort order and store it in a dedicated input. In other words, the output should look like this, if the overall param were named images:

"images" => [{"thumb" => url, "full" => url, "detail" => "data"}, {"thumb" => url, "full" => url, "detail" => "data"}, {"thumb" => url, "full" => url, "detail" => "data"}, ...]

I’ve tried a number of different approaches, but none seem to work. Most turn the form into an array with a single hash element, which doesn’t work because the hash doesn’t preserve the key order. Here’s a sampling of the formats I used and the results they gave:

<input type="hidden" name="images[][bar]" value="2">
# "images"=>[{"bam"=>"2", "baz"=>"2", "bar"=>"2"}]

<input type="hidden" name="images[][bar][a]" value="2">
# "images"=>[{"bam"=>{"a"=>"2", "b"=>"2"}, "baz"=>{"a"=>"2", "b"=>"2"}, "bar"=>{"a"=>"2", "b"=>"2"}}]

<input type="hidden" name="images[][a][bar][][a]" value="2">
# "images"=>[{"a"=>{"bam"=>[{"a"=>"2", "b"=>"2"}], "baz"=>[{"a"=>"2", "b"=>"2"}], "bar"=>[{"a"=>"2", "b"=>"2"}]}}]

<input type="hidden" name="images[][][bar][][a]" value="2">
# "images"=>[{"bam"=>[{"a"=>"2", "b"=>"2"}], "baz"=>[{"a"=>"2", "b"=>"2"}], "bar"=>[{"a"=>"2", "b"=>"2"}]}]

<input type="hidden" name="images[][bam]" value="2">
<input type="hidden" name="images[][bar][a]" value="2">
<input type="hidden" name="images[][bar][b]" value="2">
# malformed result!
# /!\ FAILSAFE /!\  Sun Nov 21 11:05:44 +0100 2010
# Status: 500 Internal Server Error
# can't convert nil into Hash

<input type="hidden" name="images[][bam][]" value="2">
<input type="hidden" name="images[][bar][a]" value="2">
<input type="hidden" name="images[][bar][b]" value="2">
# ditto

I traced the error I generated with the two last examples back and found myself looking at the normalize_params method in Rack’s utils.rb (this code is the same in both Rack 1.0.1 and 1.2.1). This is where Rails/Rack parses the form data and turns them into our familiar params hash. Ignoring the error, I took a look at how the parsing is done:

def normalize_params(params, name, v = nil)
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
  k = $1 || ''
  after = $' || ''

  return if k.empty?

  if after == ""
    params[k] = v
  elsif after == "[]"
    params[k] ||= []
    raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
    params[k] << v
  elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
    child_key = $1
    params[k] ||= []
    raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
    if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
      normalize_params(params[k].last, child_key, v)
    else
      params[k] << normalize_params({}, child_key, v)
    end
  else
    params[k] ||= {}
    raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
    params[k] = normalize_params(params[k], after, v)
  end

  return params
end

I’m not a regular expressions master, but if I understand this right, what I want to do doesn’t seem possible.

Assuming so, it seems the best solution seems to be to give each image (represented as a param hash) an order parameter; when the user moves elements around, I’d crawl each order parameter and update the order value so it can be stored appropriately by the server. It’s less elegant, but it’ll work.

I’d be very grateful for any opinions/experience — is my original design indeed impossible?

Alex