Rails Multiple CSV Export Buttons in Single View (i.e. Two or More) - ruby-on-rails

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

Related

How to generate a CSV file from Mongoid data in Rails

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.

When CSV.generate, generate empty field without ""

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

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.

Changing Output for FasterCSV

I currently have a controller that will handle a call to export a table into a CSV file using the FasterCSV gem. The problem is the information stored in the database isn't clear sometimes and so I want to change the output for a particular column.
My project.status column for instance has numbers instead of statuses ie 1 in the database corresponds to Active, 2 for Inactive and 0 for Not Yet decided. When I export the table it shows 0,1,2 instead of Active, Inactive or Not Yet decided. Any idea how to implement this?
I tried a simple loop that would check the final generated CSV file and change each 0,1,2 to its corresponding output, but the problem is every other column that had a 0,1,2 would change as well. I'm not sure how to isolate the column.
Thanks in advance
def csv
qt = params[:selection]
#lists = Project.find(:all, :order=> (params[:sort] + ' ' + params[:direction]), :conditions => ["name LIKE ? OR description LIKE ?", "%#{qt}%", "%#{qt}%"])
csv_string = FasterCSV.generate(:encoding => 'u') do |csv|
csv << ["Status","Name","Summary","Description","Creator","Comment","Contact Information","Created Date","Updated Date"]
#lists.each do |project|
csv << [project.status, project.name, project.summary, project.description, project.creator, project.statusreason, project.contactinfo, project.created_at, project.updated_at]
end
end
filename = Time.now.strftime("%Y%m%d") + ".csv"
send_data(csv_string,
:type => 'text/csv; charset=UTF-8; header=present',
:filename => filename)
end
This is actually fairly easy. In your controller code:
#app/controllers/projects_controller.rb#csv
#lists.each do |project|
csv << [project.descriptive_status, project.name, project.summary, project.description, project.creator, project.statusreason, project.contactinfo, project.created_at, project.updated_at]
end
Then in your model code. You probably already have a method that decodes the DB status to a more descriptive one though:
#app/models/project.rb
ACTIVE_STATUS = 0
INACTIVE_STATUS = 1
NOT_YET_DECIDED_STATUS = 2
def descriptive_status
case status
when ACTIVE_STATUS
"Active"
when INACTIVE_STATUS
"Inactive"
when NOT_YET_DECIDED_STATUS
"Not Yet Decided"
end
end
There are probably a number of ways you can then refactor this. In the controller at least, it would probably be best to make that finder a more descriptive named scope. The constants in the model could be brought into SettingsLogic configuration or another similar gem.

Resources