I have the below background job that writes to a csv file and emails it out. I am using the Tempfile class so the file is removed after I email it to the user. Currently, when I look at the csv file I am producing the results look like the following:
["Client Application" "Final Price" "Tax" "Credit" "Base Price" "Billed At" "Order Guid" "Method of Payment Guid" "Method of Payment Type"]
["web" nil nil nil nil nil nil "k32k313k1j3" "credit card"]
Please ignore the data, but the issue is, it is being written directly to the file in the ruby format and not removing the "" and [] characters.
Please see the code below:
class ReportJob
#queue = :report_job
def self.perform(client_app_id, current_user_id)
user = User.find(current_user_id)
client_application = Application.find(client_app_id)
transactions = client_application.transactions
file = Tempfile.open(["#{Rails.root}/tmp/", ".csv"]) do |csv|
begin
csv << ["Application", "Price", "Tax", "Credit", "Base Price", "Billed At", "Order ID", "Payment ID", "Payment Type"]
transactions.each do |transaction|
csv << "\n"
csv << [application.name, transaction.price, transaction.tax, transaction.credit, transaction.base_price, transaction.billed_at, transaction.order_id, transaction.payment_id, transaction.payment_type]
end
ensure
ReportMailer.send_rev_report(user.email, csv).deliver
csv.close(unlink_now=false)
end
end
end
end
Would this be an issue with using the tempfile class instead of the csv class? or is there something I could do to change the way it is being written to the file?
Adding the code for reading the csv file in the mailer. I am currently getting a TypeError that says "can't convert CSV into String".
class ReportMailer < ActionMailer::Base
default :from => "test#gmail.com"
def send_rev_report(email, file)
attachments['report.csv'] = File.read("#{::Rails.root.join('tmp', file)}")
mail(:to => email, :subject => "Attached is your report")
end
end
end
The issue is that you're not actually writing csv data to the file. You're sending arrays to the filehandle. I believe you need something like:
Tempfile.open(....) do |fh|
csv = CSV.new(fh, ...)
<rest of your code>
end
to properly setup the CSV output filtering.
Here's how I did it.
patient_payments = PatientPayment.all
Tempfile.new(['patient_payments', '.csv']).tap do |file|
CSV.open(file, 'wb') do |csv|
csv << patient_payments.first.class.attribute_names
patient_payments.each do |patient_payment|
csv << patient_payment.attributes.values
end
end
end
I prefer to do
tempfile = Tempfile.new(....)
csv = CSV.new(tempfile, ...) do |row|
<rest of your code>
end
try this:
Tempfile.open(["#{Rails.root}/tmp/", ".csv"]) do |outfile|
CSV::Writer.generate(outfile) do |csv|
csv << ["Application", "Price", "Tax", "Credit", "Base Price", "Billed At", "Order ID", "Payment ID", "Payment Type"]
#...
end
end
Related
In ruby 3.1.2, I have this csv file:
make,model,color,doors,email
dodge,charger,black,4,practice1#whatever.com
ford,focus,blue,5,practice2#whatever.com
nissan,350z,black,2,practice3#whatever.com
mazda,miata,white,2,practice4#whatever.com
honda,civid,brown,4,practice5#whatever.com
corvette,stingray,red,2,practice6#whatever.com
ford,fiesta,blue,5,practice7#whatever.com
bmw,m4,black,2,practice8#whatever.com
audi,a5,blue,2,practice9#whatever.com
subaru,brz,black,2,practice10#whatever.com
lexus,rc,black,2,practice11#whatever.com
My current program allows a user to input an email and from that email it will go to that line and change the value of the model of a car, and then print out the csv onto the screen. What can be done to make the code also update the actual CSV file in the directory?
This is my code:
require "csv"
csv = CSV.read('cars.csv', headers: true)
print "Enter an email: \n> "
demo = gets.chomp
#print csv.find {|row| row['email'] == demo}
got = csv.find {|row| row['email'] == demo}
p got
p got.fields
p got['model']
got['model'] = 'rcf'
p got['model']
p got.fields
#prints out the csv file
puts csv.to_s
if my coding approach is different and if there's a better way don't hesitate to change any of my coding or completely not use it and go another path, please comment the code
Please refer below for your reference:
csv = CSV.read('cars.csv', headers: true)
print "Enter an email: \n> "
demo = gets.chomp
csv.map{|row| row['model'] = 'rcf' if row['email'] == demo}
puts csv.to_s
CSV.open("cars.csv", "wb") do |new_csv|
new_csv << %w[make model color doors email]
csv.each do |row|
new_csv << row
end
end
I hope this will help you.
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)
I'm attempting to concatenate a string to an array value. Since the URL/domain is the same, I simply store the users email prefix and append the url/domain. I need to export the full email address out to CSV:
CSV.generate(options) do |csv|
columns = %w(name, email_address)
url = "#example.com"
all.each do |location|
csv << location.attributes.values_at(*columns) + [url]
end
end
Currently the resulting output is:
Joe, user1, #example.com
Bob, user2, #example.com
What I need is:
Joe, user1#example.com
Bob, user2#example.com
How can I achieve the above?
location is a model object, right?
CSV.generate(options) do |csv|
domain = "example.com"
all.each do |location|
csv << [location.name, "#{location.email_address}##{domain}"]
end
end
Update:
IMO that's cleaner as well. But if you want to keep your version, then I suggest you create a full_email_address method in your Location model which returns something like username#domain.com.
Then, you can vary the columns data later on and easily modify your CSV output. Like so:
class Location << ActiveRecord::Base
def full_email_address
return "" if self.email_address.blank?
domain = "example.com" # or save this as a constant in the class
"#{self.email_address}##{domain}"
end
end
CSV.generate(options) do |csv|
columns = %w{name full_email_address} # add other methods or attributes here
all.each do |location|
csv << columns.map{ |moa| location.public_send(moa) }
end
end
Try this:
CSV.generate(options) do |csv|
columns = %w(name, email_address)
url = "#example.com"
all.each do |location|
row = location.attributes.values_at(*columns)
row[-1] = row[-1] + url
csv << row
end
end
Currently the array written to CSV is ['Joe', 'user1'] + ['#example.com'], so instead of adding url to the attributes array I am adding it to the last attribute.
I have a total of 7 columns with 6 columns initially filled out in a CSV file that I'm writing. When I try to populate the 7th column with data, I keep running into this error:
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.<<
Why do I keep running into this error? I should be able to write values into a 'nil'/blank space on a CSV file. Below is my code:
#Finds two records in the database
accounts = Account.find(1,2)
spammer_status = []
#Makes a call into the akismet API and populates spammer_status array with
#true or false values if the person is a spammer or not.
accounts.each do |accounts|
spammer_status << client.comment_check(accounts.last_seen_ip, nil,
:comment_author => accounts.name,
:comment_author_email => accounts.email,
:comment_author_url => accounts.url,
:comment_content => accounts.about)
end
#Changes the values from booleans to strings
spammer_status.map! { |value| value.to_s }
#Populates the initial 6 columns from the database values
CSV.open("/var/local/openhub/current/script/akismet_results.csv","w") do |row|
row << ["id",
"name",
"email",
"url",
"description",
"last seen ip",
"spammer status"]
accounts.each do |accounts|
row << [accounts.id,
accounts.name,
accounts.email,
accounts.url,
accounts.about,
accounts.last_seen_ip]
end
end
#Attempts to populate the 7th column, nil error
CSV.foreach("/var/local/openhub/current/script/akismet_results.csv", headers:true) do |row|
# binding.pry
row[6] << spammer_status.shift
end
What am I doing wrong here? The error is on the foreach part of the program. All I want to do is to iterate a row at a time and then add the string converted booleans to the correct column. Any help would be appreciated?
You are trying to << to a nil object. row[6] is nil. I believe you just want to assign a value to row[6] or if you want to use <<, just do it to row itself.
CSV.foreach("/var/local/openhub/current/script/akismet_results.csv", headers:true) do |row|
# binding.pry
row[6] = spammer_status.shift
# or you could do row << spammer_status.shift
end
I eventually figured this issue out by refactoring my code. I have to admit that the above variation was not written very well. By extracting the spam status as a method and then creating a method call when the CSV is written, I was able to make the functionality work.
def is_spam?(account)
spammer_status = client.comment_check(account.last_seen_ip, nil,
:comment_author => account.name,
:comment_author_email => account.email,
:comment_author_url => account.url,
:comment_content => account.about)
spammer_status.to_s
end
CSV.foreach("/var/local/openhub/current/script/akismet_results.csv","a", headers: true) do |row|
row << [account.id,
account.name,
account.email,
account.url,
account.about,
account.last_seen_ip,
is_spam?(account)]
end
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