Using Model.where with self.id or similar when exporting to .CSV - ruby-on-rails

I am trying to export data to CSV based on the current case_main "show" page the export button is on. I believe I need something similar to the following, but I can't get any permutation of it to work. Any attempt at referencing self.id or :id results in an error or nil.
Billedtime.where(case_main_id: self.id).each do |time|
Using Billedtime.all.each do |time| lets the code run, but it obviously grabs everything instead of only billedtimes for the current case_main.
case_main has two one-to-manys, billedtimes and lineitems. I am trying to combine billedtimes + lineitems for a particular case_main into a single .CSV export.
Here is the code:
Model:
def self.to_csv
desired_columns = ["Client Code",
"Case Name" ,
"Date",
"Description",
"Hours",
"Charge",
]
CSV.generate(headers: true) do |csv|
# Header columns
csv << desired_columns
# Data columns
Billedtime.where(case_main_id: self.id).each do |time|
csv <<
[
time.case_main.client.client_code,
time.case_main.case_name,
time.billedtime_date,
time.billedtime_description,
time.billedtime_hours,
time.billedtime_total
]
end
Lineitem.where(case_main_id: self.id).each do |line|
csv <<
[
line.case_main.client.client_code,
line.case_main.case_name,
line.lineitem_date.to_date,
line.lineitem_description,
' ',
line.lineitem_total
]
end
end
end
Controller:
def show
#case_mains = CaseMain.where(:id => #case_main.id).limit(5)
respond_to do |format|
format.html
format.csv { send_data #case_mains.to_csv, filename: "case-#{Date.today}.csv"}
end
end
View:
<%= link_to "Export Billed Items", case_main_path(format: "csv") %>
Thanks!

The issue is that your model doesn't poses an id attribute, so self.id doesn't do the job. Instead you want to grab the current scope using all and iterate through the collection using each or find_each. To prevent an 1+N query I've also added the needed includes call.
def self.to_csv
CSV.generate(headers: true) do |csv|
csv << [
"Client Code",
"Case Name",
"Date",
"Description",
"Hours",
"Charge"
]
all.includes(:client, :billedtimes, :lineitems).find_each do |case_main|
case_main.billedtimes.each do |billedtime|
csv << [
case_main.client.client_code,
case_main.case_name,
billedtime.billedtime_date,
billedtime.billedtime_description,
billedtime.billedtime_hours,
billedtime.billedtime_total
]
end
case_main.lineitems.each do |lineitem|
csv << [
case_main.client.client_code,
case_main.case_name,
lineitem.lineitem_date.to_date,
lineitem.lineitem_description,
' ',
line.lineitem_total
]
end
end
end
end
You might think, I don't want to export all records. However all (without receiver) operates on the current scope, so when you use it in the context CaseMain.where(id: #case_main.id).limit(5).to_csv you will grab the collection CaseMain.where(id: #case_main.id).limit(5).all.

Related

How to check header exist before import data in Ruby CSV?

I want to write header only 1 time in first row when import data to csv in ruby, but the header is written many time on output file.
job_datas.each do |job_data|
#company_job = job data coverted etc....
save_job_to_csv(#company_job)
end
def save_job_to_csv(job_data)
filepath = "tmp/jobs/jobs.csv"
CSV.open(filepath, "a", :headers => true) do |csv|
if csv.blank?
csv << CompanyJob.attribute_names
end
csv << job_data.attributes.values
end
end
Any one can give me solution? Thank you so much!
You are calling save_job_to_csv the method for each job_data and pushing header every time csv << CompanyJob.attribute_names
filepath = "tmp/jobs/jobs.csv"
CSV.open(filepath, "a", :headers => true) do |csv|
# push header once
csv << CompanyJob.attribute_names
# push every job record
job_datas.each do |job_data|
#company_job = job data coverted etc....
csv << #company_job.attributes.values
end
end
The above script can be created wrapped a method but if you like to write a separate method that just saves the CSV, then you need to refactor the script when you first prepare an array of values holding header and pass it to a method that just saves to CSV.
You could do something similar to this:
def save_job_to_csv(job_data)
filepath = "tmp/jobs/jobs.csv"
unless File.file?(filepath)
File.open(filepath, 'w') do |file|
file.puts(job_data.attribute_names.join(','))
end
end
CSV.open(filepath, "a", :headers => true) do |csv|
csv << job_data.attributes.values
end
end
It just checks beforehand if the file exists and if not it adds the header. If you want tabs as column separators, you just have to change the value for the join function and add the col_sep parameter to CSV.open():
file.puts(job_data.attribute_names.join("\t"))
CSV.open(filepath, "a", :headers => true, col_sep: "\t") do |csv|

How do I add a custom column to my CSV.generate endpoint in Rails app?

I followed this example but I'm having issues with a nil value showing up in each row (in between the original values and the additional value I'm adding.
Here's my controller endpoint:
def daily_grocery_carts_overview_export
#data = DailyRetailerShop.all.order("start_date ASC")
respond_to do |format|
format.html { redirect_to root_path }
format.csv { send_data #data.to_csv, filename: "DailyGroceryCarts-#{Time.now.strftime("%Y%m%d%H%M%S")}.csv" }
end
end
Here's my model:
class DailyRetailerShop < ActiveRecord::Base
def self.to_csv
# generate site abbreviations & name to add to CSV file
site_abbreviations = {}
Partner.all.each do |p|
site_abbreviations[p[:site_abbreviation]] = p[:name]
end
CSV.generate do |csv|
# remove certain columns
export_columns = column_names - %w(id site_abbreviation created_at updated_at)
# add custom column header
headers = export_columns << 'Website'
# add to csv file
csv << headers
all.each do |item|
row = item.attributes.values_at(*export_columns).insert(-1, site_abbreviations[item.site_abbreviation])
csv << row
end
end
end
end
When I download the CSV file and open it up I see a blank value followed by the custom value I appended in each row. If I read the downloaded CSV file, here's what I see in the first few rows:
data = CSV.read("downloaded_file.csv")
puts data[0..3]
=> [["start_date", "grocery_retailer", "retailer_shops", "Website"], ["2019-10-15", "walmart", "25", nil, "Website1"], ["2019-10-15", "walmart", "24", nil, "Website2"], ["2019-10-15", "instacart", "23", nil, "Website3"]]
Notice there is a nil value for each row (not in the headers). If I exclude my custom header name and then append values as I did above those nil values (blanks when I open the file) are no longer there.
So, it appears the custom header creating a nil value for each row. How do I get rid of that?
I never found out why those nil values are being included in each row but I figured out how to restrict them from showing up. Here's how I modified the function within my model:
class DailyRetailerShop < ActiveRecord::Base
def self.to_csv
# generate site abbreviations & name to add to CSV file
site_abbreviations = {}
Partner.all.each do |p|
site_abbreviations[p[:site_abbreviation]] = p[:name]
end
CSV.generate do |csv|
# remove certain columns
export_columns = column_names - %w(id site_abbreviation created_at updated_at)
# add custom column header
headers = export_columns << 'Website'
# add to csv file
csv << headers
all.each do |item|
# exclude the last item before inserting site name (custom header)
row = item.attributes.values_at(*export_columns)[0..-2].insert(-1, site_abbreviations[item.site_abbreviation])
csv << row
end
end
end
end
Hope this helps someone in the future!

Is there any optimized way to export CSV of more than 100K record using Rails?

I have 200k locations in my database. So I want to export all the locations into CSV format. While doing this it is taking too much time to download. What is the best way to optimize code in rails?
In controller:
def index
all_locations = Location.all
respond_to do |format|
format.csv { send_data all_locations.to_csv, filename: "locations-#{Date.today}.csv" }
end
end
In model
def self.to_csv
attributes = %w{id city address}
CSV.generate(headers: true) do |csv|
csv << ['Id', 'City', 'Address']
all.each do |location|
csv << attributes.map{ |attr| location.send(attr) }
end
end
end
I ran your code with some adjustments with my own data. I made the following changes, and using benchmarking I came to a 7x increase.
Your model:
def self.to_csv
attributes = %w{id city address}
CSV.generate(headers: true) do |csv|
csv << ['Id', 'City', 'Address']
all.pluck(attributes).each { |data| csv << data }
end
end
By using pluck you only get the data you want, and then you push all that data into the csv array.
if you are using Postgresql then you can use this in application_record.rb
def self.to_csv_copy(attrs="*", header=[])
rc = connection.raw_connection
rv = header.empty? ? [] : ["#{header.join(',')}\n"]
sql = self.all.select(attrs).to_sql
rc.copy_data("copy (#{sql}) to stdout with csv") do
# rubocop:disable AssignmentInCondition
while line = rc.get_copy_data
rv << line
end
end
rv.join
end
and then do
Location.to_csv_copy(%w{id city address}, ['Id', 'City', 'Address'])
It is even faster than the above solution.

Exporting data to CSV from multiple models

I am able to export all fields of a model to a CSV file, but now I need to add some attributes from another model which has a has_many relationship with the original.
my controller file looks like
respond_to do |format|
format.html
format.csv { send_data #students.as_csv, filename: "students-#{Date.today}.csv" }
end
student.rb
def self.as_csv
attributes = %w{surname given_name admission_year admission_no hobbies }
CSV.generate do |csv|
csv << attributes
all.each do |item|
csv << item.attributes.values_at(*attributes)
end
end
It works fine but because hobby is another table having a has_many relation with student as a student has many hobbies, I want to show hobbies for each student as a comma separated list in the csv. I am stuck as to how to achieve this.
Any help will be appreciated.
I would just do something like this:
CSV_HEADER = %w[surname given_name admission_year admission_no hobbies]
def self.as_csv
CSV.generate do |csv|
csv << CSV_HEADER
all.each do |student|
csv << [
student.surname,
student.given_name,
student.admission_year,
student.admission_no,
student.hobbies.pluck(:title).join(', ')
]
end
end
end
You may need to adjust title with a attributes name that returns the hobby as a string.

Active admin CSV export custom query scoped

Am using active admin Export CSV option. Its returning all the values related to the particular table.
I want the reports only for a particular month.
Can anyone help?
you can write own csv exporter
collection_action :download_report, :method => :get do
users = User.where('created_at >= ?', Date.today - 1.month)
csv = CSV.generate( encoding: 'Windows-1251' ) do |csv|
# add headers
csv < [ #Some header ]
# add data
users.each do |user|
csv << [ user.created_at ]
end
end
# send file to user
send_data csv.encode('Windows-1251'), type: 'text/csv; charset=windows-1251; header=present', disposition: "attachment; filename=report.csv"
end
action_item only: :index do
link_to('csv report'), params.merge(:action => :download_report))
end
index :download_links => false do
# off standard download link
end
this is just example for you. Your code can be another
for generation csv file use this code where you want
# generate csv file of photo
def self.generate_csv
header = []
csv_fname = "#{CSV_FILE_PATH}/images.csv"
options = {headers: :first_row}
photo_columns = column_names - ["id", "updated_at"]
photo_columns.map{|col| col == "created_at" ? header << "ScrapeDate" : header << col.classify}
CSV.open(csv_fname, "w", options ) do |csv|
csv << header if File.exist?(csv_fname) && File.size(csv_fname) == 0
find_each(batch_size: 5000) do |photo|
csv << photo.attributes.values_at(*photo_columns)
end
end
end
in above code which column you don't want subtract that cols from actual cols, for example column_names - ["id", "updated_at"] here column_names return actual cols array and which cols we don't need we subtract them.

Resources