Last Updated: February 25, 2016
·
1.251K
· hololeap

Structs with type-casting in Ruby

Sometimes you have some data like this:

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

And you organize it with a Struct object:

require 'csv'

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

But, your data ends up looking like this:

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

Wouldn't it be nice to have Struct automatically cast each argument to a specified data type? Lacking any ubiquitous method for casting data in Ruby, we can do the next best thing and specify a method to call. Meet 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

Now you can create a struct that takes a hash in the format field_name => method.

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

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

Now the object in each field is an instance of the correct class

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

Much better :)