Estruturas com fundição de tipo em Ruby

Às vezes você tem alguns dados como estes:

Dave,32,employee,15.75
Harris,23,employee,15.75
Mary,38,supervisor,21.25

E você o organiza com um objeto Struct:

require 'csv'

Person = Struct.new :name, :age, :category, :wage
people
= CSV.parse(data).map { |args| Person.new(*args) }

Mas seus dados ficam assim:

#<struct Person name="Dave", age="32", category="employee" ... >
#<struct Person name="Harris", age="23", category="employee" ... >
...

Não seria bom ter o Struct lançando automaticamente cada argumento para um tipo de dados especificado? Na falta de qualquer método onipresente para lançar dados em Ruby, podemos fazer a próxima melhor coisa e especificar um método a ser chamado. Conheça CastingStruct:

class CastingStruct < Struct
def self.new(hash, &blk)
super(*hash.keys) do
define_method
:initialize do |*args|
super *hash.values.map { |method|
args
.shift.public_send method
}
end
class_eval
(&blk) if blk
end
end
end

Agora você pode criar uma estrutura que recebe um hash no formato field_name => método .

Person = CastingStruct.new name:     :to_s, 
age
: :to_i,
category
: :to_sym,
wage
: :to_f

people
= CSV.parse(data).map { |args| Person.new(*args) }

Agora, o objeto em cada campo é uma instância da classe correta

irb> people.first
=> #<struct Person name="Dave", age=32, category=:employee, wage=15.75>
irb
> people.first.category.class
=> Symbol

Muito melhor 🙂