FasterCSV layout - ruby-on-rails

I need to layout my CSV into columns not rows. So going down the spreadsheet not across. For example:
Header 1, value1.1, value2.1
Header 2, value1.2, value2.2
Header 3, value1.3, value2.3
Does anyone know how to do this? I've been through the documentation and can't find anything about changing the layout to columns.
EDIT:
row_data = [];
csv_string = FasterCSV.generate do |csv|
# header row
row_data << ["id", "Name", "Age"]
# data rows
Playerapplication.find_each do |player|
row_data << [player.id, player.name, player.age]
end
row_data.transpose
csv << row_data
end
# send it to the browser
send_data csv_string,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=players_application.csv"

Simply use Array#transpose on your data before writing to CSV.
If you modify your code like this:
row_data = [];
csv_string = FasterCSV.generate do |csv|
# header row
row_data << ["id", "Name", "Age"]
# data rows
Playerapplication.find_each do |player|
row_data << [player.id, player.name, player.age]
end
row_data.transpose.each do |row|
csv << row
end
end
it works for me.

Related

Export Ruby Hash to CSV [duplicate]

I am new in ruby so please forgive the noobishness.
I have a CSV with two columns. One for animal name and one for animal type.
I have a hash with all the keys being animal names and the values being animal type. I would like to write the hash to the CSV without using fasterCSV. I have thought of several ideas what would be easiest.. here is the basic layout.
require "csv"
def write_file
h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
CSV.open("data.csv", "wb") do |csv|
csv << [???????????]
end
end
When I opened the file to read from it I opened it File.open("blabla.csv", headers: true)
Would it be possible to write back to the file the same way?
If you want column headers and you have multiple hashes:
require 'csv'
hashes = [{'a' => 'aaaa', 'b' => 'bbbb'}]
column_names = hashes.first.keys
s=CSV.generate do |csv|
csv << column_names
hashes.each do |x|
csv << x.values
end
end
File.write('the_file.csv', s)
(tested on Ruby 1.9.3-p429)
Try this:
require 'csv'
h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
CSV.open("data.csv", "wb") {|csv| h.to_a.each {|elem| csv << elem} }
Will result:
1.9.2-p290:~$ cat data.csv
dog,canine
cat,feline
donkey,asinine
I think the simplest solution to your original question:
def write_file
h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
CSV.open("data.csv", "w", headers: h.keys) do |csv|
csv << h.values
end
end
With multiple hashes that all share the same keys:
def write_file
hashes = [ { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' },
{ 'dog' => 'rover', 'cat' => 'kitty', 'donkey' => 'ass' } ]
CSV.open("data.csv", "w", headers: hashes.first.keys) do |csv|
hashes.each do |h|
csv << h.values
end
end
end
CSV can take a hash in any order, exclude elements, and omit a params not in the HEADERS
require "csv"
HEADERS = [
'dog',
'cat',
'donkey'
]
def write_file
CSV.open("data.csv", "wb", :headers => HEADERS, :write_headers => true) do |csv|
csv << { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
csv << { 'dog' => 'canine'}
csv << { 'cat' => 'feline', 'dog' => 'canine', 'donkey' => 'asinine' }
csv << { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine', 'header not provided in the options to #open' => 'not included in output' }
end
end
write_file # =>
# dog,cat,donkey
# canine,feline,asinine
# canine,,
# canine,feline,asinine
# canine,feline,asinine
This makes working with the CSV class more flexible and readable.
I tried the solutions here but got an incorrect result (values in wrong columns) since my source is a LDIF file that not always has all the values for a key. I ended up using the following.
First, when building up the hash I remember the keys in a separate array which I extend with the keys that are not allready there.
# building up the array of hashes
File.read(ARGV[0]).each_line do |lijn|
case
when lijn[0..2] == "dn:" # new record
record = {}
when lijn.chomp == '' # end record
if record['telephonenumber'] # valid record ?
hashes << record
keys = keys.concat(record.keys).uniq
end
when ...
end
end
The important line here is keys = keys.concat(record.keys).uniq which extends the array of keys when new keys (headers) are found.
Now the most important: converting our hashes to a CSV
CSV.open("export.csv", "w", {headers: keys, col_sep: ";"}) do |row|
row << keys # add the headers
hashes.each do |hash|
row << hash # the whole hash, not just the array of values
end
end
[BEWARE] All the answers in this thread are assuming that the order of the keys defined in the hash will be constant amongst all rows.
To prevent problems (that I am facing right now) where some values are assigned to the wrong keys in the csv (Ex:)
hahes = [
{:cola => "hello", :colb => "bye"},
{:colb => "bye", :cola => "hello"}
]
producing the following table using the code from the majority (including best answer) of the answers on this thread:
cola | colb
-------------
hello | bye
-------------
bye | hello
You should do this instead:
require "csv"
csv_rows = [
{:cola => "hello", :colb => "bye"},
{:colb => "bye", :cola => "hello"}
]
column_names = csv_rows.first.keys
s=CSV.generate do |csv|
csv << column_names
csv_rows.each do |row|
csv << column_names.map{|column_name| row[column_name]} #To be explicit
end
end
Try this:
require 'csv'
data = { 'one' => '1', 'two' => '2', 'three' => '3' }
CSV.open("data.csv", "a+") do |csv|
csv << data.keys
csv << data.values
end
Lets we have a hash,
hash_1 = {1=>{:rev=>400, :d_odr=>3}, 2=>{:rev=>4003, :d_price=>300}}
The above hash_1 having keys as some id 1,2,.. and values to those are again hash with some keys as (:rev, :d_odr, :d_price).
Suppose we want a CSV file with headers,
headers = ['Designer_id','Revenue','Discount_price','Impression','Designer ODR']
Then make a new array for each value of hash_1 and insert it in CSV file,
CSV.open("design_performance_data_temp.csv", "w") do |csv|
csv << headers
csv_data = []
result.each do |design_data|
csv_data << design_data.first
csv_data << design_data.second[:rev] || 0
csv_data << design_data.second[:d_price] || 0
csv_data << design_data.second[:imp] || 0
csv_data << design_data.second[:d_odr] || 0
csv << csv_data
csv_data = []
end
end
Now you are having design_performance_data_temp.csv file saved in your corresponding directory.
Above code can further be optimized.

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=>"pelnar#gmail.com",
: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=>"chris#gmail.com",
: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.
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] }
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

How to append to CSV in Ruby

I've got below code:
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << %w{ id email title first_name last_name position work_phone company state industry mobile origin terms events roles booths }
all.each do |user|
events = '', roles = '', booths = ''
events = user.events.first.name.to_s if user.events.present?
roles = user.roles.first.name.to_s if user.roles.present?
booths = user.booths.first.name.to_s if user.booths.present?
csv << user.attributes.values_at("id", "email", "title", "first_name", "last_name", "position", "work_phone", "company", "state", "industry", "mobile", "origin", "terms")
csv << events
csv << roles
csv << booths
end
end
end
I want to be able to generate csv and add those values in the extra columns but I'm getting undefined method 'map' for "admin":String error.
Is there a way to append this to the csv on the same row?
CSV#<< says :
The primary write method for wrapped Strings and IOs, row (an Array or CSV::Row) is converted to CSV and appended to the data source. When a CSV::Row is passed, only the row’s fields() are appended to the output.
But you are passing stirngs. see below :
csv << events # string
csv << roles # string
csv << booths # string
Tried to replicate the erro :
require 'csv'
a = CSV.generate("") do |csv|
csv << "foo"
end
# `<<': undefined method `map' for "foo":String (NoMethodError)
Here is a fix :
require 'csv'
a = CSV.generate("") do |csv|
csv << ["foo"] # just wrapped the string into an Array as doc is saying.
end
a # => "foo\n"
Write your code as :
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << %w{ id email title first_name last_name position work_phone company state industry mobile origin terms events roles booths }
all.each do |user|
ary = %w[events,roles,booths].map do |item|
user.send(item).first.name if user.send(item).present?
end
row = user.attributes.values_at("id", "email", "title", "first_name", "last_name", "position", "work_phone", "company", "state", "industry", "mobile", "origin", "terms")
row.push(*ary)
csv << row
end
end
end
When you append to csv it's expecting an array that represents a row or a CSV::Row object. First, build the array, then append that to csv as follows:
row = user.attributes.values_at("id", "email", "title", "first_name", "last_name", "position", "work_phone", "company", "state", "industry", "mobile", "origin", "terms")
row << events
row << roles
row << booths
csv << row

Export CSV with Mongoid and FasterCSV

Class UserController
def export_users
users = User.all
stream_csv do |csv|
csv << ["Name","Email","Gender"]
users.each do |i|
csv << [i.name,i.email,i.gender]
end
end
end
def stream_csv
require 'fastercsv'
filename = params[:action] + ".csv"
#this is required if you want this to work with IE
if request.env['HTTP_USER_AGENT'] =~ /msie/i
headers['Pragma'] = 'public'
headers["Content-type"] = "text/plain"
headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
headers['Expires'] = "0"
else
headers["Content-Type"] ||= 'text/csv'
headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
controller.response.headers["Content-Transfer-Encoding"] = "binary"
end
render :text => Proc.new { |response, output|
csv = FasterCSV.new(output, :row_sep => "\r\n")
yield csv
}
end
end
Err: "#Proc:0x9382539#/sites/app/controllers/export_controller.rb:56"
Using Ruby 1.8 and Rails 3.0.9
So I think the problem here is that I'm not using "Proc" right. Or it's not supposed to act like just another block...
I thought about programming a new logic into the class so that reads better. But if somebody could explain to me why my code is wrong or at least point me in a new direction than I might be able to learn something new here. Thanks
Note: Found a better way:
def export_inverts
require 'fastercsv'
inverts = Invert.all
filename = params[:action] + ".csv"
#this is required if you want this to work with IE
if request.env['HTTP_USER_AGENT'] =~ /msie/i
headers['Pragma'] = 'public'
headers["Content-type"] = "text/plain"
headers['Cache-Control'] = 'no-cache, must-revalidate, post-check=0, pre-check=0'
headers['Content-Disposition'] = "attachment; filename=\"#{filename}\""
headers['Expires'] = "0"
else
headers["Content-Type"] ||= 'text/csv'
headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
headers["Content-Transfer-Encoding"] = "binary"
end
csv_string = FasterCSV.generate do |csv|
csv << ["Genus","Species","Common Name","Pet Name","Gender"]
inverts.each do |i|
csv << [i.scientific_name,i.scientific_name,i.common_name,i.pet_name,i.gender]
end
end
render :text => csv_string
end
Yield can only be used inside a function or a block. Yield is used in a function that takes a block to say, yield some value into the block. Actually it says yield this value into the proc that the block has been converted into with the ampersand operator (in most cases). However, you could pass a Proc to a function that was expecting it.
Here, you just want to return the value from the proc and "yield" isn't needed.

Calling def in Ruby for exporting CSV

I currently have some code which iv used to export a table from the data I have
require 'fastercsv'
def dump_csv
#users = User.find(:all, :order => "lastname ASC")
#outfile = "members_" + Time.now.strftime("%m-%d-%Y") + ".csv"
csv_data = FasterCSV.generate do |csv|
csv << [
"Last Name",
"First Name",
"Username",
"Email",
"Company",
"Phone",
"Fax",
"Address",
"City",
"State",
"Zip Code"
]
#users.each do |user|
csv << [
user.lastname,
user.firstname,
user.username,
user.email,
user.company,
user.phone,
user.fax,
user.address + " " + user.cb_addresstwo,
user.city,
user.state,
user.zip
]
end
end
send_data csv_data,
:type => 'text/csv; charset=iso-8859-1; header=present',
:disposition => "attachment; filename=#{#outfile}"
flash[:notice] = "Export complete!"
end
my question is how do I call it from my view and will this work with will_pagination. I know FasterCVS creates tables using the ActiveRecord so will_paginiation wont be of any use when trying to organize the table.
I don't understand why you are talking about will_paginate...
But if you want to send data or send a file from a controller, you should look at the methods send_data and send_file :
http://api.rubyonrails.org/classes/ActionController/Streaming.html
Thanks for your help. I saw your link and came up with this:
#lists = Project.find(:all, :order=> (params[:sort] + ' ' + params[:direction]), :conditions => ["name || description LIKE ?", "%#{params[:selection]}%"])
csv_string = FasterCSV.generate do |csv|
csv << ["Status","Name","Summary","Description","Creator","Comment","Contact Information","Created Date","Updated Date"]
#lists.each do |project|
csv << [project.status, project.name, project.summary, project.description, project.creator, project.statusreason, project.contactinfo, project.created_at, project.updated_at]
end
end
filename = Time.now.strftime("%Y%m%d") + ".csv"
send_data(csv_string,
:type => 'text/csv; charset=utf-8; header=present',
:filename => filename)
end

Resources