I've gone for a fairly simple proxy based approach that doesn't do any  
real converting. When converting an array of records we return a  
wrapper object.
When you call (for example) name on that object you get back a proxy  
object that just stores that name.
When you then call [3] on that proxy object, the proxy retrieves the  
object at index 3 and calls the previously remembered method name on it.

The reverse conversion is similar. the conversion function just  
returns a wrapper object, and wrapper[2] is just a proxy that  
remembers that index. When you call a method on the proxy
it forwards it to the record of arrays to obtain the appropriate array  
and retrieves the index element. A limitation is that the wrapper  
object just understands the [] method. It doesn't for example know how  
many elements it contains.

Nothing particular about OpenStruct is relied upon (eg activerecord  
objects work just fine, or just any old objects for example eg,  
roa_to_aor(aor_to_roa(data)) works)
My wrappers also allow assigning (the assignment gets passed through  
to the original object)

usage example:
#setup some test data
require 'ostruct'
data = []
data << OpenStruct.new( :name => 'fred', :color => 'red', :age => 22)
data << OpenStruct.new( :name => "paul", :color => "blue", :age => 30)
data << OpenStruct.new( :name => 'henry', :color => 'pink', :age => 23)
data << OpenStruct.new( :name => 'claire', :color => 'orange', :age =>  
19)

rec = OpenStruct.new
rec.name = %w(fred paul henry)
rec.age = [10,20,30]
rec.food = %w(pizza cake burger)

#real stuff begins here
x = aor_to_roa data
x.name[2] #=> "henry"
x.name[2] = 'sarah'
data[2] #> => #<OpenStruct name="sarah", color="pink", age=23>

y = roa_to_aor rec
y[1].age #=> 20
y[2].food = 'ice-cream'
rec.food #=> ["pizza", "cake", "ice-cream"]

#Composing:

x = roa_to_aor(aor_to_roa(data))
x[1].name #=> "paul"
x[1].name = "charlie"
data[1].name => "charlie"

y= aor_to_roa(roa_to_aor(rec))
y.name[1] #=> "paul"
y.name[1] = "alice"
rec.name #=> ["fred", "alice", "henry"]


With aor_to_roa I succumbed to a desire for cuteness, so for example

x.name #=> ["fred", "paul", "henry", "claire"]
x.name.class #=> Array (Even though it isn't)
x.name.collect {|s| s.upcase} #=> ["FRED", "PAUL", "HENRY", "CLAIRE"]

ie it can really look like an array if you want it to (but this isn't  
very efficient as it's materializing the array over and over again.  
this could probably be improved on). I didn't do the same for  
roa_to_aor.
If one was feeling particularly enthusiastic, sticking some  
pluralization rules into method missing would allow one to do (given  
the above data) x.names[2] rather than x.name[2] which reads slightly  
nicer

Fred

CODE:

def roa_to_aor(rec)
   RecordOfArraysWrapper.new(rec)
end

def aor_to_roa(arr)
   ArrayOfRecordsWrapper.new(arr)
end


class ArrayOfRecordsWrapper
   def initialize(array)
     @array = array
   end

   def method_missing(name, *args)
     FieldProxy.new(@array, name)
   end

   class FieldProxy
     instance_methods.each { |m| undef_method m unless m =~ /(^__)/ }

     def initialize array, name
       @array, @name = array, name
     end

     def [](index)
       @array[index].send @name
     end

     def []=(index,value)
       @array[index].send(@name.to_s + '=', value)
     end

     def to_a
       @field_array = @array.collect {|a| a.send @name}
     end

     def method_missing(*args, &block)
       to_a.send *args, &block
     end
   end
end


class RecordOfArraysWrapper
   def initialize(record)
     @record = record
   end

   def [](index)
     RecordProxy.new(@record, index)
   end

   class RecordProxy
     def initialize(record, index)
       @record, @index = record, index
     end

     def method_missing(name, *args)
       if name.to_s =~ /(.*)=$/
         @record.send($1)[@index]=args.first
       else
         @record.send(name)[@index]
       end
     end
   end
end