I have a Ruby on Rails application running with Mongodb. I have a model called Label and I have added some data to it.
I am trying to generate a CSV file from the index method in the Labels controller.
I added the following code in the index method:
respond_to do |format|
format.html
format.csv { send_data #labels.to_csv, filename: "labels-#{Date.today}.csv" }
end
I added a method to_csv in the controller:
def self.to_csv(options={})
attributes = %w{name manufacturing_date digital_signature1}
#all_labels=Label.all
csv << attributes
#all_labels.each do |label|
csv << attributes.map{ |attr| label.send(attr) }
end
end
end
But when I actually generate a CSV file, it gives me a file where there is no data. I get six column names like:
#<Label:0x00007f637ac2e130>, #<Label:0x00007f637ac2e090>, #<Label:0x00007f637ac2dfc8>, #<Label:0x00007f637ac2db18>, #<Label:0x00007f637ac2d9d8>, #<Label:0x00007f637ac2d668>
What am I doing wrong?
If you are following https://www.codementor.io/#victor_hazbun/export-records-to-csv-files-ruby-on-rails-vda8323q0 the to_csv method should be added to the model, not to the controller.
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!
Ruby 2.2, Ruby on Rails 4.2
I'm genarating some CSV data in Ruby on Rails, and want empty fields to be empty, like ,, not like ,"", .
I wrote codes like below:
somethings_cotroller.rb
def get_data
respond_to do |format|
format.html
format.csv do
#data = SheetRepository.accounts_data
send_data render_to_string, type: :csv
end
end
end
somethings/get_data.csv.ruby
require 'csv'
csv_str = CSV.generate do |csv|
csv << [1,260,37335,'','','','','','']
...
end
And this generates CSV file like this.
get_data.csv
1,260,37335,"","","","","",""
I want CSV data like below.
1,260,37335,,,,,,
It seems like Ruby adds "" automatically.
How can I do this??
In order to get CSV to output an empty column, you need to tell it that nothing is in the column. An empty string, in ruby, is still something, you'll need to replace those empty strings with nil in order to get the output you want:
csv_str = CSV.generate do |csv|
csv << [1,260,37335,'','','','','',''].map do |col|
col.respond_to?(:empty?) && col.empty? ? nil : col
end
end
# => 1,260,37335,,,,,,
In rails you can clean that up by making use of presence, though this will blank out false as well:
csv_str = CSV.generate do |csv|
csv << [1,260,37335,'',false, nil,'','',''].map(&:presence)
end
# => 1,260,37335,,,,,,
The CSV documentation shows an option that you can use for this case. There are not examples but you can guess what it does.
The only consideration is, you need to send an array of Strings, otherwise, you will get a NoMethodError
csv_str = CSV.generate(write_empty_value: nil) do |csv|
csv << [1,260,37335,'','','','','','', false, ' ', nil].map(&:to_s)
end
=> "1,260,37335,,,,,,,false, ,\n"
The benefit of this solution is, you preserve the false.
I resolved by myself!
in somethings_controller.rb
send_data render_to_string.gsub("\"\"",""), type: :csv
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.
I've followed Railscasts and the similar GoRails videos--searched SO for [rails] [csv] export to no avail. There's a similar but hard-to-follow question/answer called Two Export Buttons to CSV in Rails. I am able to add a single button to export my table records to csv (have chosen all records for this button).
I want to add a second (or nth) button to my view page to export a subset of table records...e.g. Event.where(country: some_country) in addition to the current (working fine) button/download that exports all records of a model. I thought this would be a common use of a csv export.
Here's what I have, working backwards:
Events Index view: I have a download button to export to CSV (added helper code for the glyph image)
<%= link_to glyph(:save), "events.csv", class: "btn btn-success" %>
If I want a second action, I need a different route than events.csv, right? So, I tried a new button changing "events.csv" to #events_us.csv.
Events Controller: have defined the 'what' and respond as all records (using ransack and will_paginate gems too):
def index
#search = Event.ransack(params[:q])
#events = #search.result.paginate(:page => params[:page], :per_page => 50)
#events_all = Event.all
respond_to do |format|
format.html # need to have html
format.csv { send_data #events_all.to_csv, filename: "Events-#{Date.today}.csv" }
end
end
Should I add a second format.csv in the respond_to do...? Tried that (i.e. defined a different instance variable like #events_us = Event.where(country: "US"), to no avail. Seems weird to have two format.csvs though. And, I get No route matches [GET] "/#events_us.csv"
I probably don't need to say much more, as I'm all kinds of lost on this one.
Event Model: use all scope, csv library, etc to organize the csv:
def self.to_csv
column_names = Event.column_names.map(&:to_s) - %w[id created_at updated_at]
CSV.generate(headers: true) do |csv|
csv << column_names
all.each do |record|
csv << record.attributes.values_at(*column_names)
end
end
end
If I have a second respond_to action in the controller, could I have a second method like self.to_csv_us?
def self.to_csv_us
column_names = Event.column_names.map(&:to_s) - %w[id created_at updated_at]
CSV.generate(headers: true) do |csv|
csv << column_names
#events_us.each do |record|
csv << record.attributes.values_at(*column_names)
end
end
end