Rails CSV Export - Adding custom field - ruby-on-rails

I'm building an ecommerce app in Rails. I have a method to export the Order model with shipping details. The below method works as-is but I want to add a column into the export from another model.
My order model has a product_id which joins with the product table that has the product name. I want to add the product name. So something like order.product.name. How would I go about adding that into the below?
Here is my method in my order.rb:
def self.to_csv(orders)
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns)
end
end
end

You can simply add it to your line, like so:
def self.to_csv(orders)
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns.insert(-1, :product_name)
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns).insert(-1, order.product.name)
end
end
end
The insert method inserts the given values before the element with the given index.
Negative indices count backwards from the end of the array, where -1 is the last element.
It returns the resulting array.
To simplify, in this case, this:
wanted_columns = [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip]
CSV.generate do |csv|
csv << wanted_columns.insert(-1, :product_name)
end
Should behave exactly the same as:
CSV.generate do |csv|
csv << [:id, :shipname, :shipaddress, :shipcity, :shipstate, :shipzip, :product_name]
end
The same principal applies to the array generated by:
order.attributes.values_at(*wanted_columns)
If this is not working, try the simplified example, and inspect the array for correctness prior to adding it to the array. You may additionally simplify:
orders.each do |order|
csv << order.attributes.values_at(*wanted_columns).insert(-1, order.product.name)
end
to:
csv << orders.first.attributes.values_at(*wanted_columns).insert(-1, orders.first.product.name)
For purposes of troubleshooting...

Related

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!

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.

export hash array to csv file

My objective is to transform a hash array to a csv file.
This is my controller:
respond_to do |format|
format.html
format.csv { send_data #comsumptions.to_csv }
end
#comsumptions is a hash array:
[{"x"=>76,
"y"=>"example",
"z"=>2015,
"consumption"=>#<BigDecimal:7fea4a1cadb8,'0.5382857142E4',18(27)>},
{"x"=>76,
"y"=>"example2",
"z"=>2015,
"consumption"=>#<BigDecimal:7fea4a1ca7c8,'0.5437E4',9(27)>},(..)
I want to create a CSV file with 2 specific columns, "consumption" and "z".
When I did this with these 3 lines comment the output is a file with all the #consumptions. How can I select these 2 columns and transform in a cv file?
def self.to_csv
CSV.generate(headers: true ) do |csv|
#csv << column_names
#all.each do |product|
# csv << product.attributes.values_at(*column_names)
end
end
end
From your feedback, I think the best way here is creating a csv view file in your views. For example, if your html file is comsumptions.html.erb, then your csv view file should be comsumptions.csv.ruby
# comsumptions.csv.ruby
require 'csv'
CSV.generate do |csv|
csv << ['consumption', 'z']
#comsumptions.each do |c|
csv << [ c['consumption'].to_s, c['z'] ]
end
end
And we need to change the controller too. Remove respond_to part or modify it as follows
respond_to do |format|
format.html
format.csv
end
I already tested on my localhost, and this should work!
require 'csv'
#comsumptions =
[{"x"=>76,
"y"=>"example",
"z"=>2015},
{"x"=>76,
"y"=>"example2",
"z"=>2015}]
class << #comsumptions
def to_csv (*keys)
keys = first.keys if keys.empty?
CSV.generate(headers: keys, write_headers: true) do |csv|
each do |e|
csv << e.values_at(*keys)
end
end
end
end
p #comsumptions.to_csv("x","y")
p #comsumptions.to_csv()
This solution heavily inspired from the one from Van Huy works fine for any array oh hashes as long as the hashes have all the same keys, undefined behaviour to be expected otherwise
What's not clear to me is where do you put the def self.to_csv method, inside the controller? That is wrong. What you want to do is either augment the #consumptions object, augment the Array class, or define a method in the controller. My example does augment the #consumptions object, you can put all that stuff inside the controller method and it should work.

Rails CSV Download - Specify columns and get full URL

I'm developing a marketplace app where sellers can list items to sell.
I have a download CSV method which lets sellers download their listings.
My method below downloads all columns. I have two questions:
1) How do I specify columns, let's say I only want name, image, price.
2) In the model, the image field just stores the image name such as item.jpg. But I want to export the URL where the image is stored. I'm using Paperclip to load images and Dropbox to save them. So each image has a dropbox url, which I want to export.
my method in the model:
def self.to_csv(listings)
CSV.generate do |csv|
csv << column_names
listings.each do |listing|
csv << listing.attributes.values_at(*column_names)
end
end
end
1) How do I specify columns, let's say I only want name, image, price.
You need to change the .values_at(*column_names) part to select only the columns you want:
def self.to_csv(listings)
wanted_columns = [:name, :image, :price]
CSV.generate do |csv|
csv << wanted_columns
listings.each do |listing|
csv << listing.attributes.with_indifferent_access.values_at(*wanted_columns)
end
end
end
2) export the image's URL in the CSV file
You probably have a method like image.url which returns the URL of the image:
def self.to_csv(listings)
wanted_columns = [:name, :price]
CSV.generate do |csv|
csv << wanted_columns + [:image]
listings.each do |listing|
attrs = listing.attributes.with_indifferent_access.values_at(*wanted_columns)
attrs.push(listing.image.url) # if image is not always present, use `listing.image.try(:url)`
csv << attrs
end
end
end

how to add custom column to CSV during generation in rails

I am currently exporting a CSV file from my rails app and it is working fine but i would like to add a bit more data to the csv.
Currently i'm using:
CSV.generate do |csv|
csv << column_names
all.each do |item|
csv << item.attributes.values_at(*column_names)
end
end
To generate a csv with all of the data from the target model but would like to add an additional column, manufacturer_name which will be taken from a parent model.. Something like:
CSV.generate do |csv|
csv << column_names,
csv << "manufacturer_name"
all.each do |item|
csv << item.attributes.values_at(*column_names),
csv << Manufacturer.find(item.manufacturer_id).first().name
end
end
How would i write this correctly so that the "manufacturer_name" get set to a new column header and the manufacturer name of each item is pulled in and put in the correct column?
CSV.generate do |csv|
names = column_names << 'manufacturer_name'
csv << names
all.each do |item|
row = item.attributes.values_at(*column_names)
row << Manufacturer.find(item.manufacturer_id).first.name
csv << row
end
end

Resources