How to generate a CSV file from Mongoid data in Rails - ruby-on-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.

Related

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

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

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

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.

Ruby, Tempfile, CSV

I have the below resque job that produces a csv file and sends it to a mailer. I want to validate that the csv file has data so I do not email blank files. For some reason, when I write a method outside of the perform method, it will not work. For example, the below code will print invalid when I know the csv file has data on the first line. If I uncomment the line below ensure it works properly, however I want to extract this checking of the file into a separate method. Is this correct?
class ReportJob
#queue = :report_job
def self.perform(application_id, current_user_id)
user = User.find(current_user_id)
client_application = Application.find(client_application_id)
transactions = application.transactions
file = Tempfile.open(["#{Rails.root}/tmp/", ".csv"]) do |csv|
begin
csv_file = CSV.new(csv)
csv_file << ["Application", "Price", "Tax"]
transactions.each do |transaction|
csv_file << [application.name, transaction.price, transaction.tax]
end
ensure
ReportJob.email_report(user.email, csv_file)
#ReportMailer.send_report(user.email, csv_file).deliver
csv_file.close(unlink=true)
end
end
end
def self.email_report(email, csv)
array = csv.to_a
if array[1].blank?
puts "invalid"
else
ReportMailer.send_report(email, csv).deliver
end
end
end
You should invoke your method as such:
ReportJob.email_report(email, csv)
Otherwise, get rid of the self in:
def self.email_report(email, csv)
# your implementation here.
end
and define your method as follows:
def email_report(email, csv)
# your implementation.
end
This is something that we call Class Methods and Instance Methods.

How can I save the response created by my Rails application?

There is CSV-export of some objects (such as tasks, contacts, etc) in my application. It just renders CSV-file like this:
respond_to do |format|
format.html
format.csv { render text: Task.to_csv } # I have self.to_csv def in model
end
It generates a CSV file when I go to '/tasks.csv' without a problem.
Now I want to export all the objects and zip them. I'm using rubyzip gem to create zip-files. Now my code for creating zip-file with all the CSVs looks like that:
Zip::ZipFile.open("#{path_to_file}.zip", Zip::ZipFile::CREATE) do |zipfile|
zipfile.file.open("tasks.csv", "w") { |f| f << open("http://#{request.host}:#{request.port.to_s}/tasks.csv").read }
# the same lines for contacts and other objects
end
But it seems that there is something wrong with it because it's executing for a long time (I'm getting Timeout::Error even if there is just one line in CSV) and the resulting zip-archive contains something broken.
How can I save my "/tasks.csv", "/contacts.csv", etc as a file on server (inside of zip-archive in this case)?
I did it! The code is:
Zip::ZipFile.open("#{path_to_file}.zip", Zip::ZipFile::CREATE) do |zipfile|
zipfile.file.open("tasks.csv", "w") do |f|
CSV.open(f, "w") do |csv|
CSV.parse(Task.to_csv) { |row| csv << row }
end
end
end

Resources