How to remove redundant file open operation in ruby - 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)

Related

Refactoring my code so that file closes automatically once loaded, how does the syntax work?

My program loads a list from a file, and I'm trying to change the method so that it closes automatically.
I've looked at the Ruby documentation, the broad stackoverflow answer, and this guy's website, but the syntax is always different and doesn't mean much to me yet.
My original load:
def load_students(filename = "students.csv")
if filename == nil
filename = "students.csv"
elsif filename == ''
filename = "students.csv"
end
file = File.open(filename, "r")
file.readlines.each do |line|
name, cohort = line.chomp.split(",")
add_students(name).to_s
end
file.close
puts "List loaded from #{filename}."
end
My attempt to close automatically:
def load_students(filename = "students.csv")
if filename == nil
filename = "students.csv"
elsif filename == ''
filename = "students.csv"
end
open(filename, "r", &block)
line.each do |line|
name, cohort = line.chomp.split(",")
add_students(name).to_s
end
puts "List loaded from #{filename}."
end
I'm looking for the same result, but without having to manually close the file.
I don't think it'll be much different, so how does the syntax work for automatically closing with blocks?
File.open(filename, 'r') do |file|
file.readlines.each do |line|
name, cohort = line.chomp.split(",")
add_students(name).to_s
end
end
I’d refactor the whole code:
def load_students(filename = "students.csv")
filename = "students.csv" if filename.to_s.empty?
File.open(filename, "r") do |file|
file.readlines.each do |line|
add_students(line.chomp.split(",").first)
end
end
puts "List loaded from #{filename}."
end
Or, even better, as suggested by Kimmo Lehto in comments:
def load_students(filename = "students.csv")
filename = "students.csv" if filename.to_s.empty?
File.foreach(filename) do |line|
add_students(line.chomp.split(",").first)
end
puts "List loaded from #{filename}."
end

Copy CSV content to another CSV

I'm trying to copy the content of a CSV file to another one.
First attempt:
FileUtils.cp(source_file, target_file)
Second attempt:
CSV.open(target_file, "w") {|file| file.truncate(0) }
CSV.open(target_file, "a+") do |csv|
CSV.foreach(source_file, :headers => true) do |row|
csv << row
end
end
Third attempt:
CSV.open(target_file, "w") {|file| file.truncate(0) }
File.open(source_file, "rb") do |input|
File.open(target_file, "wb") do |output|
while buff = input.read(4096)
output.write(buff)
end
end
end
Nothing is happening. What am I doing wrong?
When I run from Terminal cp source_file target_file, the target file is correctly created/copied. But in my app it is just creating the target_file, without any content.

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.

Find a specific text in a file, cut it from the current file and then paste it another file

I am trying find a certain piece of code in a .rb file, once found I want to cut it from the current file and then paste it into another existing file. So an an example:
file1.rb has the following:
RSpec.describe 'Get Test Data' do
it "should get test data for build" do |example|
log_start_test("#{example.description}")
get_test_data
log_complete_test("#{example.description}")
end
end
I want to find it "should get test data for build" do |example| and then cut this piece of code:
it "should get test data for build" do |example|
log_start_test("#{example.description}")
get_test_data
log_complete_test("#{example.description}")
end
and paste it another file.
So far I have been able to find the desired string using something like this:
File.open("#{Dir.pwd}/spec/api/test_data_search_spec.rb") do |f|
f.each_line do |line|
if line =~ /do |example|/
puts "Found root #{line}"
end
end
end
Just not able to figure out the exact regular expression to find the required block and then how do i do a cut from a file and paste into another one? Any ideas would be great.
regular expressions are not suited to parse code.
you could use method_source as an existing solution to the problem.
Thanks #phoet. That would have worked for specific methods but I was looking more for moving rspec example code block around. But here is what I ended up using as an example:
def move_example_block(example_description, source_file,destination_file)
lines = File.readlines("#{Dir.pwd}/spec/api/#{source_file}_spec.rb")
desired_block = lines.join[/it "should get test data for build" do(.*)end/m]
temp = desired_block.freeze
puts temp
filename = "#{Dir.pwd}/spec/api/#{source_file}_spec.rb"
text = File.read(filename)
puts = text.gsub(/it "#{example_description}" do(.*)end/m, "end")
File.open(filename, "w") { |file| file << puts }
filename = "#{Dir.pwd}/spec/api/#{destination_file}_spec.rb"
text = File.read(filename)
puts = text.gsub(/end/, temp)
File.open(filename, "w") { |file| file << puts }
end

Parsing data to a csv file on a new line - 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

Resources