Parsing data to a csv file on a new line - ruby - ruby

I am trying to export data that I 'get' into a new csv file. Currently, my code below posts everyone onto a single line until it fills up and then it continues to the next line.
I would like to have it where when data is imported, it starts on the following line below, creating a list of transactions.
def export_data
File.open('coffee_orders.csv', 'a+') do |csv|
puts #item_quantity = [Time.now, #item_name, #amount]
csv << #item_quantity
end
end

Basing it on your starting code, I'd do something like:
def export_data
File.open('coffee_orders.csv', 'a') do |csv|
csv << [Time.now, #item_name, #amount].join(', ')
end
end
Or:
def export_data
File.open('coffee_orders.csv', 'a') do |csv|
csv << '%s, %s, %s' % [Time.now, #item_name, #amount].map(&:to_s)
end
end
Notice, it's not necessary to use 'a+' to append to a file. Instead use 'a' only unless you absolutely need "read" mode while the file is open also. Here's what the IO.new documentation says:
"a" Write-only, starts at end of file if file exists,
otherwise creates a new file for writing.
"a+" Read-write, starts at end of file if file exists,
otherwise creates a new file for reading and
writing.
The way I'd write it for myself would be something like:
CSV_FILENAME = 'coffee_orders.csv'
def export_data
csv_has_content = File.size?(CSV_FILENAME)
CSV.open(CSV_FILENAME, 'a') do |csv|
csv << %w[Time Item Amount] unless csv_has_content
csv << [Time.now, #item_name, #amount]
end
end
This uses Ruby's CSV class to handle all the ins-and-outs. It checks to see if the file already exists, and if it has no content it writes the header before writing the content.

Try this. It will add a new line after each transaction. When you append to it next, it will be from a new line.
def export_data
File.open('coffee_orders.csv', 'a+') do |csv|
csv.puts #item_quantity = [Time.now, #item_name, #amount]
end
end
Although by looking the extension, you would probably want to confine it to csv format.
def export_data
File.open('coffee_orders.csv', 'a+') do |csv|
#item_quantity = [Time.now, #item_name, #amount]
csv.puts #item_quantity.join(',')
end
end

Related

How to remove redundant file open operation in ruby

I made a ruby program to copy content of one CSV file to a new CSV file.
This is my code -
require 'csv'
class CopyFile
def self.create_duplicate_file(file_name)
CSV.open(file_name, "wb") do |output_row|
output_row << CSV.open('input.csv', 'r') { |csv| csv.first }
CSV.foreach('input.csv', headers: true) do |row|
output_row << row
end
end
end
end
puts "Insert duplicate file name"
file_name = gets.chomp
file_name = file_name+".csv"
CopyFile.create_duplicate_file(file_name)
puts "\nDuplicate File Created."
I am opening the input.csv file twice, one to copy headers and then to copy content.
I want to optimise my code. So is there a way to optimise it further?
Just use the cp method:
FileUtils.cp(src, destination, options), no need to reinvent the wheel, like this:
class CopyFile
def self.create_duplicate_file(file_name)
FileUtils.cp('input.csv',file_name)
end
end
or better yet:
file_name = gets.chomp
file_name = file_name+".csv"
FileUtils.cp('input.csv', file_name)

How do I access the filename of the CSV file I just opened?

I have a method that looks like this:
def extract_websites
websites = []
csvs = Dir["#{#dir_name}/#{#state}/*.csv"]
csvs.each do |csv|
CSV.foreach(csv, headers: true) do |row|
websites << row['Website']
end
end
websites.uniq!
end
But what I need want to do is for each CSV file that is opened, I would like to detect the name of that file.
How do I do that?
In your sample the variable csv holds the path of the CSV file.
That local variable is available in the blocks of its children, it shares its scope down but not upwards.
So:
def extract_websites
websites = []
csvs = Dir["#{#dir_name}/#{#state}/*.csv"]
csvs.each do |csv|
puts File.expand_path(csv) # show the full path for each csv file
CSV.foreach(csv, headers: true) do |row|
puts csv # shows unexpanded path for each row of a csv
websites << row['Website']
end
end
websites.uniq!
end
should print out the path for each CSV file and for each row.

How to map and edit a CSV file with Ruby

Is there a way to edit a CSV file using the map method in Ruby? I know I can open a file using:
CSV.open("file.csv", "a+")
and add content to it, but I have to edit some specific lines.
The foreach method is only useful to read a file (correct me if I'm wrong).
I checked the Ruby CSV documentation but I can't find any useful info.
My CSV file has less than 1500 lines so I don't mind reading all the lines.
Another answer using each.with_index():
rows_array = CSV.read('sample.csv')
desired_indices = [3, 4, 5].sort # these are rows you would like to modify
rows_array.each.with_index(desired_indices[0]) do |row, index|
if desired_indices.include?(index)
# modify over here
rows_array[index][target_column] = 'modification'
end
end
# now update the file
CSV.open('sample3.csv', 'wb') { |csv| rows_array.each{|row| csv << row}}
You can also use each_with_index {} insead of each.with_index {}
Is there a way to edit a CSV file using the map method in Ruby?
Yes:
rows = CSV.open('sample.csv')
rows_array = rows.to_a
or
rows_array = CSV.read('sample.csv')
desired_indices = [3, 4, 5] # these are rows you would like to modify
edited_rows = rows_array.each_with_index.map do |row, index|
if desired_indices.include?(index)
# simply return the row
# or modify over here
row[3] = 'shiva'
# store index in each edited rows to keep track of the rows
[index, row]
end
end.compact
# update the main row_array with updated data
edited_rows.each{|row| rows_array[row[0]] = row[1]}
# now update the file
CSV.open('sample2.csv', 'wb') { |csv| rows_array.each{|row| csv << row}}
This is little messier. Is not it? I suggest you to use each_with_index with out map to do this. See my another answer
Here is a little script I wrote as an example on how read CSV data, do something to data, and then write out the edited text to a new file:
read_write_csv.rb:
#!/usr/bin/env ruby
require 'csv'
src_dir = "/home/user/Desktop/csvfile/FL_insurance_sample.csv"
dst_dir = "/home/user/Desktop/csvfile/FL_insurance_sample_out.csv"
puts " Reading data from : #{src_dir}"
puts " Writing data to : #{dst_dir}"
#create a new file
csv_out = File.open(dst_dir, 'wb')
#read from existing file
CSV.foreach(src_dir , :headers => false) do |row|
#then you can do this
# newrow = row.each_with_index { |rowcontent , row_num| puts "# {rowcontent} #{row_num}" }
# OR array to hash .. just saying .. maybe hash of arrays..
#h = Hash[*row]
#csv_out << h
# OR use map
#newrow = row.map(&:capitalize)
#csv_out << h
#OR use each ... Add and end
#newrow.each do |k,v| puts "#{k} is #{v}"
#Lastly, write back the edited , regexed data ..etc to an out file.
#csv_out << newrow
end
# close the file
csv_out.close
The output file has the desired data:
USER#USER-SVE1411EGXB:~/Desktop/csvfile$ ls
FL_insurance_sample.csv FL_insurance_sample_out.csv read_write_csv.rb
The input file data looked like this:
policyID,statecode,county,eq_site_limit,hu_site_limit,fl_site_limit,fr_site_limit,tiv_2011,tiv_2012,eq_site_deductible,hu_site_deductible,fl_site_deductible,fr_site_deductible,point_latitude,point_longitude,line,construction,point_granularity
119736,FL,CLAY COUNTY,498960,498960,498960,498960,498960,792148.9,0,9979.2,0,0,30.102261,-81.711777,Residential,Masonry,1
448094,FL,CLAY COUNTY,1322376.3,1322376.3,1322376.3,1322376.3,1322376.3,1438163.57,0,0,0,0,30.063936,-81.707664,Residential,Masonry,3
206893,FL,CLAY COUNTY,190724.4,190724.4,190724.4,190724.4,190724.4,192476.78,0,0,0,0,30.089579,-81.700455,Residential,Wood,1
333743,FL,CLAY COUNTY,0,79520.76,0,0,79520.76,86854.48,0,0,0,0,30.063236,-81.707703,Residential,Wood,3
172534,FL,CLAY COUNTY,0,254281.5,0,254281.5,254281.5,246144.49,0,0,0,0,30.060614,-81.702675,Residential,Wood,1

Parse remote file with FasterCSV

I'm trying to parse the first 5 lines of a remote CSV file. However, when I do, it raises Errno::ENOENT exception, and says:
No such file or directory - [file contents] (with [file contents] being a dump of the CSV contents
Here's my code:
def preview
#csv = []
open('http://example.com/spreadsheet.csv') do |file|
CSV.foreach(file.read, :headers => true) do |row|
n += 1
#csv << row
if n == 5
return #csv
end
end
end
end
The above code is built from what I've seen others use on Stack Overflow, but I can't get it to work.
If I remove the read method from the file, it raises a TypeError exception, saying:
can't convert StringIO into String
Is there something I'm missing?
Foreach expects a filename. Try parse.each
You could manually pass each line to CSV for parsing:
require 'open-uri'
require 'csv'
def preview(file_url)
#csv = []
open(file_url).each_with_index do |line, i|
next if i == 0 #Ignore headers
#csv << CSV.parse(line)
if i == 5
return #csv
end
end
end
puts preview('http://www.ferc.gov/docs-filing/eqr/soft-tools/sample-csv/contract.txt')

How to parse a CSV file, update a field, then save

I need to read a CSV file, update a field, then save the changes. I have everything working fine except saving my changes to the field I'm updating:
require 'csv'
#parsed_file = CSV::Reader.parse(File.open("#{RAILS_ROOT}/doc/some.csv"))
#parsed_file.each_with_index do |row, x|
address = row[5]
l = Location.address_find(address)
if l != nil
puts "#{l.name} at #{l.address}"
row[14] = l.store_code
puts row[14]
else
puts "No matching address Found!!!"
end
#What do I do here? Something like this? CSV::Writer.generate(#parsed_file)
end
What do I do here? How do I save the changes I made and update the file?
What I would do is write the updated records to a new file and then, if you want, after you have finished your program can delete the old file and rename the new file to have the original file name.
To do this I would open the output file at the top of your code, outside the each_with_index loop. e.g.
csv_out = CSV::Writer.generate(File.open('new.csv', 'wb'))
Then inside your each_with_index loop you can write the current row to the new file like this:
csv_out << row
Then at the end you can close the new file:
csv_out.close
As mentioned in the comments, CSV::Writer is no longer in the standard library. An equivalent version of the code using the newer CSV.foreach (for reading) and CSV.open (for writing) is:
CSV.open("path/to/updated_file.csv", "wb") do |csv_out|
CSV.foreach("#{RAILS_ROOT}/doc/some.csv") do |row|
address = row[5]
l = Location.address_find(address)
if l != nil
puts "#{l.name} at #{l.address}"
row[14] = l.store_code
puts row[14]
else
puts "No matching address Found!!!"
end
csv_out << row
end
end

Resources