From: Frederick Cheung Date: 2008-07-21T02:25:24+09:00 Subject: Re: [QUIZ] Records and Arrays (#170) 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] #> => # 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