Ctpelnar1988
Ctpelnar1988

Reputation: 1255

How to convert an array of objects to CSV in Rails

I have an array of objects. I am trying to create CSV data and allow the user to download that file but I get the following error:

Undefined method 'first_name' for Hash:0x007f946fc76590

      employee_csv_data.each do |obj|
        csv << attributes.map{ |attr| obj.send(attr) }
      end
    end
  end

This is the button that allows a user to download the CSV:

<%= link_to "Download Employee CSV", download_employee_csv_path %>

Controller:

def download_employee_csv
  employee_csv_data = []
  employees.each do |employee|
    employee_csv_data << {
        first_name: employee[:first_name],
        last_name: employee[:last_name],
        email: employee_email,
        phone1: employee[:phone1],
        gender: employee[:gender],
        veteran: employee[:veteran].to_s,
        dob: employee[:dob],
        core_score: service_score,
        performance_rank: rank,
        industry_modules_passed: industry_modules_passed
      }
  end 

  respond_to do |format|
    format.html
    format.csv { send_data Employer.to_csv(employee_csv_data), filename: "download_employee_csv.csv" }
  end
end

employee_csv_data:

=> [{:first_name=>"Christopher",
  :last_name=>"Pelnar",
  :email=>"[email protected]",
  :phone1=>"4072422433",
  :gender=>"male",
  :veteran=>"true",
  :dob=>"1988-09-09",
  :core_score=>"No Score",
  :performance_rank=>"No Rank",
  :industry_modules_passed=>"No Industry Modules Passed"},
 {:first_name=>"chris",
  :last_name=>"pelnar",
  :email=>"[email protected]",
  :phone1=>"4072422433",
  :gender=>"male",
  :veteran=>"true",
  :dob=>"1998-09-09",
  :core_score=>"729",
  :performance_rank=>"Good",
  :industry_modules_passed=>"Entry-Service, Entry-Tech"}]

Model:

def self.to_csv(employee_csv_data)
    attributes = %w(first_name last_name email phone gender veteran dob core_score performance_rank industry_modules_passed)

    CSV.generate(headers: true) do |csv|
      csv << attributes

      employee_csv_data.each do |obj|
        csv << attributes.map{ |attr| obj.send(attr) }
      end
    end
  end

When I click the button, it takes me to the blank HTML page without any problem. When I add .csv to the filename in the URL on that page I get the error.

Upvotes: 1

Views: 2618

Answers (2)

tommyh
tommyh

Reputation: 181

I adapted @Ctpelnar1988's answer to determine the attributes dynamically and allow each array item to have different columns:

def array_of_hashes_to_csv(array)
  array_keys = array.map(&:keys).flatten.uniq

  CSV.generate(headers: true) do |csv|
    csv << array_keys

    array.each do |obj|
      csv << array_keys.map{ |attr| obj[attr] }
    end
  end
end

Example:

puts array_of_hashes_to_csv([
  {attr_a: 1, attr_b: 2},
  {attr_a: 3, attr_c: 4}
])

attr_a,attr_b,attr_c
1,2,
3,,4

In the more specific "employee_csv_data" context, I think it'd look like this:

def self.to_csv(employee_csv_data)
  attributes = employee_csv_data.map(&:keys).flatten.uniq

  CSV.generate(headers: true) do |csv|
    csv << attributes

    employee_csv_data.each do |obj|
      csv << attributes.map { |attr| obj[attr] }
    end
  end
end

Upvotes: 3

Jimmy Baker
Jimmy Baker

Reputation: 3255

It looks like it's an array of Hashes. To access properties of a hash in Ruby you need to use brackets. Try updating your code to this:

csv << attributes.map{ |attr| obj.send([], attr) }

or more concisely:

csv << attributes.map{ |attr| obj[attr] }

One more thing, in the example you provided, the keys in the hash are symbols which means you may need to convert your attributes to symbols when trying to access them, like this:

csv << attributes.map{ |attr| obj[attr.to_sym] }

Upvotes: 2

Related Questions