Maximo Chalbaud
Maximo Chalbaud

Reputation: 23

How can i add an Array into a column of a CSV file in Ruby?

i'm building a CSV file using ruby and i have different columns, i.e.

"company_name", "company_phones"

The company name is no issue since it's a simple string, but the company has more than one phone, which in my code is being stored as

phones = [phone1, phone2]

When i add it into the CSV as follows:

      CSV.open("output.csv", "a") do |csv|
    csv << [
      company_name,
      company_phones,
    ]
  end

I've tried different ways to do it but it ends up:

1) Showing up with double double quotes ([""Phone1"", ""Phone2""]) 2) And this, when reading the CSV, shows like ["\"Phone1"", "\"Phone2""] and it's not even an array but a string.

Is there something i'm missing, or it just can't be done writing CSV and i should change the format of my output?

Upvotes: 0

Views: 9983

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110685

This situation calls for a CSV custom converter.

Write the CSV file

Let's first create a CSV file from the following information.

FName = 't.csv'

headers = ["company", "vals", "phone_nbrs"]
rows = [
  ['ABC', [1.3, 2.1, 4.1], ["800-555-1000", "800-555-1001"]],
  ['XYZ', [7.3, 9.5], ["800-555-2000", "800-555-2001"]] 
]

I will do that by converting each of the arrays in rows to a string containing elements of the array converted to strings and separated by a space. For example, [1.3, 2.1, 4.1] will be saved as the string:

[1.3, 2.1, 4.1].join(' ')
  #=> "1.3 2.1 4.1"

As will be seen, this particular string representation of arrays is arbitrary, just one among many ways of doing that. For example we could save the array of floats shown above as "1.3:2.1:4.1" or "1.3¯\\_(ツ)_/¯2.1¯\\_(ツ)_/¯4.1".

Let's now create the CSV file.

require 'csv'

CSV.open(FName, "w") do |csv|
  csv << headers
  rows.each { |name,vals,phone_nbrs|
    csv << [name, vals.join(' '), phone_nbrs.join(' ')] }
end

Let's see what was written.

puts File.read(FName)
company,vals,phone_nbrs
ABC,1.3 2.1 4.1,800-555-1000 800-555-1001
XYZ,7.3 9.5,800-555-2000 800-555-2001

Read the CSV file

The trick here is to define a converter that converts the strings of strings representing floats to arrays of floats and the strings of telephone numbers to arrays of strings.

convert_arrays = lambda do |value, field|
  case field.header
  when 'vals'
    value.split.map(&:to_f)
  when 'phone_nbrs'
    value.split
  else
    value
  end
end
  #=> #<Proc:0x0000599427913ec0@(irb):19 (lambda)> 

Now read the file into an array.

CSV.foreach(FName, :headers => true, converters: [convert_arrays]).
  with_object([]) { |row, arr|
    arr << row.values_at('company', 'vals', 'phone_nbrs') }
  #=> [["ABC", [1.3, 2.1, 4.1], ["800-555-1000", "800-555-1001"]],
  #    ["XYZ", [7.3, 9.5], ["800-555-2000", "800-555-2001"]]]

See CSV::new and these examples of CSV converters.

Upvotes: 5

Related Questions