Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I can not get the exact number from a csv file if it starts with a zero in ruby

Descriptions

Read file csv in ruby.

  1. I have a csv file with this content

    longitude,latitude,phone
    13,139.7113134,35.56712836,0311112222
    
  2. I read file csv.

  3. I get data is not expect at column phone number

Code

uploaded_io = params[:rooms][:file]
rooms_table = CSV.table(uploaded_io.tempfile, encoding: "UTF-8")
rooms_table.each_with_index do |row, i|
  p row
end

puts row:

#<CSV::Row longitude:139.7113134 latitude:35.56712836 phone:52728978 >

I don't understand where is value phone number? I expect phone number is 0311112222 instead of 52728978

like image 267
Akiko Avatar asked Oct 26 '25 13:10

Akiko


2 Answers

The reason this is happening is that, per the docs, CSV.table is:

A shortcut for:

CSV.read( path, { headers:           true,
                  converters:        :numeric,
                  header_converters: :symbol }.merge(options) )

Note converters: :numeric, which tells it to automatically (attempt to) convert numeric-looking fields to Numbers. Phone numbers, of course, aren't really numbers, but rather strings of digits.

If you don't want any converstions, you could pass converters: nil as an option to CSV.table.

Assuming you do want the :numeric converter to still operate on the other fields, though, you need to define your own converter. A converter is a Proc that takes two arguments: A field value and an (optional) FieldInfo object. Your converter might look like this:

NUMERIC_EXCEPT_PHONE_CONVERTER = lambda do |value, field_info|
  if field_info.header == :phone
    value
  else
    CSV::Converters[:float].call(
      CSV::Converters[:integer].call(value))
  end
end

Then you would use it by passing it to CSV.table as the converters: option, which will override the default converters: :numeric:

rooms_table = CSV.table("data.csv", encoding: "UTF-8", converters: NUMERIC_EXCEPT_PHONE_CONVERTER)
p rooms_table[0]
# => #<CSV::Row longitude:139.7113134 latitude:35.56712836 phone:"0311112222">

As you can see, the phone value is now a string with the leading 0.

You can see this code in action on repl.it: https://repl.it/@jrunning/WellmadeFarflungCron

Aside

Why, you might ask, is this bit so ugly?

CSV::Converters[:float].call(
  CSV::Converters[:integer].call(value))

It's because the CSV module defines CSV::Converters thusly:

Converters  = {
  integer:   lambda { |f|
    Integer(f.encode(ConverterEncoding)) rescue f
  },
  float:     lambda { |f|
    Float(f.encode(ConverterEncoding)) rescue f
  },
  numeric:   [:integer, :float],
  # ...
}

Since the :numeric converter is not specified as a lambda, but rather an array that indicates that it's really just a "chain" of the :integer and :float converters, we can't just do CSV::Converters[:numeric].call(value); we have to call the two converters manually. (If anybody knows something I'm missing, please leave a comment.)

like image 63
Jordan Running Avatar answered Oct 28 '25 03:10

Jordan Running


You can change:

rooms_table = CSV.table(uploaded_io.tempfile, encoding: "UTF-8")

to:

rooms_table = CSV.table(uploaded_io.tempfile, encoding: "UTF-8", converters: nil)

which will not convert/cast your fields (you will get strings). The default converter is :numeric which does this conversions that you don't want.

Possible converters that you can work with could be found here:

https://ruby-doc.org/stdlib-2.5.1/libdoc/csv/rdoc/CSV.html#Converters

like image 40
Tarek N. Elsamni Avatar answered Oct 28 '25 02:10

Tarek N. Elsamni



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!