I have a module to generate csv file of the model when included.
# app/models/concerns/csv_exportable.rb
module CsvExportable
extend ActiveSupport::Concern
included do
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |thismodel|
csv << thismodel.attributes.values_at(*column_names)
end
end
end
end
end
and I include the module in models for which I want to generate csv file
# app/models/product.rb
class Reason < ActiveRecord::Base
include CsvExportable
.
.
end
In my controller I do
respond_to do |format|
format.html
format.csv { send_data #products.to_csv, filename: 'products.csv' }
end
I want to set the character encoding of generated csv file to Shift_JIS instead of default utf-8. What is the best way to do this ? Thank you
EDIT:
I tried
format.csv { send_data (#products.to_csv).encode("SHIFT-JIS"), filename: 'products.csv' }
now I am getting an error Encoding::ConverterNotFoundError code converter not found (UTF-8 to SHIFT-JIS)
format.csv {
send_data (#products.to_csv).encode(Encoding::SJIS),
filename: 'products.csv',
type: 'text/csv; charset=shift_jis'
}
Did the trick
You can pass encoding: '<non utf-8 encoding>' option in the options hash you passed to to_csv in CsvExportable so that it will be available for CSV.generate method while generating csv file.
Related
I'm making an export to csv file functionality in a Ruby on Rails repo and I'm almost done. However, when I press the "Export all" button, I get the undefined method `export' for nil:NilClass error. The log shows that format.csv { send_data #foos.export, filename: "foos-#{Date.today}.csv" } went wrong. What am I missing please?
This is model
class Foo < ApplicationRecord
has_many :bars
def export
[id, name, foos.map(&:name).join(' ')]
end
end
This is part of controller
def index
#foos = Foo.all
end
def export
all = Foo.all
attributes = %w{name}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |foo|
csv << attributes.map{ |attr| foo.send(attr) }
end
respond_to do |format|
format.csv { send_data #foos.export, filename: "foos-#{Date.today}.csv" }
end
end
end
def name
"#{foo_id} #{name}"
end
This is View
<button class="btn btn-success">export all</button>
This is Routes
Rails.application.routes.draw do
resources :foos
get :export, controller: :foos
root "foos#index"
end
This is Rake (lib/tasks/export.rb)
namespace :export do
task foo: :environment do
file_name = 'exported_foo.csv'
csv_data = Foo.to_csv
File.write(file_name, csv_data)
end
end
Start by creating a service object that takes a collection of records and returns CSV so that you can test the CSV generation in isolation:
# app/services/foo_export_service.rb
# Just a Plain Old Ruby Object that converts a collection of foos into CSV
class FooExportService
# The initializer gives us a good place to setup our service
# #param [Enumerable] foo - an array or collection of records
def initialize(foos)
#headers = %w{name} # the attributes you want to use
#foos = foos
end
# performs the actual work
# #return [String]
def perform
CSV.generate do |csv|
#foos.each do |foo|
csv << foo.serializable_hash.slice(#headers).values
end
end
end
# A convenient factory method which makes stubbing the
# service easier
# #param [Enumerable] foos - an array or collection of records
# #return [String]
def self.perform(foos)
new(foos).perform
end
end
# example usage
FooExportService.perform(Foo.all)
Not everything in a Rails application needs to be jammed into a model, view or controller. They already have enough responsiblities. This also lets you resuse the code for example in your rake task if you actually need it.
This simply iterates over the collection and uses Rails built in serialization features to turn the model instances into hashes that can be serialized as CSV. It also uses the fact that Hash#slice also reorders the hash keys.
In your controller you then just use the service object:
class FoosController
def export
#foos = Foo.all
respond_to do |format|
format.csv do
send_data FooExportService.perform(#foos),
filename: "foos-#{Date.today}.csv"
end
end
end
end
You don't even really need a separate export action in the first place. Just use MimeResponds to add CSV as an availble response format to the index:
class FoosController
def index
# GET /foos
# GET /foos.csv
#foos = Foo.all
respond_to do |format|
format.html
format.csv do
send_data FooExportService.perform(#foos),
filename: "foos-#{Date.today}.csv"
end
end
end
end
<%= link_to("Export as CSV", foos_path(format: :csv)) %>
i saw this code in github
require "csv"
csv_str = CSV.generate do |csv|
csv << ["awesome", "csv"]
end
result = IO.popen("secure-spreadsheet --password secret", "r+") do |io|
io.write(csv_str)
io.close_write
io.read
end
File.open("output.xlsx", "w") { |f| f.write(result) }
this code store a Excel file(output.xlsx) in my project file.
how can i convert this "store file scenario" in to "download the file in the browser"?
In your config/initializers/mime_types.rb register the xlsx mime_type(It is not available in Rails by default) :
Mime::Type.register "application/xlsx", :xlsx
Assuming your code that does the excel generation works and is in a controller method(private) named excel_file (I think its better to extract to a service/lib class):
def excel_file
csv_str = CSV.generate do |csv|
csv << ["awesome", "csv"]
end
IO.popen("secure-spreadsheet --password secret", "r+") do |io|
io.write(csv_str)
io.close_write
io.read
end
end
In your controller action you should be able to do something like this
def download_excel
respond_to do |format|
format.xlsx { send_data excel_file, type: 'application/xlsx; header=present', disposition: "attachment", filename: "output.xlsx" }
end
end
( ActionController#send_data "sends the given binary data to the browser". Read more via that link)
If you have a view, you can have a download link
<%= link_to "Download", your_download_path(format: "xlsx") %>
Users should be able to download the excel file via the link
I am working on to allow download an excel file with the below code:
login = Etc.getlogin
#dataFile = "C:/rails/#{login}data.csv"
csv1=CSV.open(#dataFile, 'w') do |csv|
$data.each do |eachrow|
csv << [eachrow.name+"#gmail.com"]
end
end
send_file(#dataFile, :filename => "#{login}data", :type => "application/csv")
Using the above code, I am able to create a file and write the data.
Instead of this, how do i write the data in csv and get downloaded into users machine instead of saving in local/server.
What you can do is generate a string with the CSV library, using CSV::generate instead of CSV::open.
Controller:
class DataController < ApplicationController
def download
respond_to do |format|
format.csv { send_csv_download }
end
end
private
def send_csv_download
string = CSV.generate do |csv|
#data.each { |row| csv << ["#{row.name}#gmail.com"] }
end
send_data string, filename: 'foo.csv', type: :csv
end
end
config/routes.rb:
get '/download', to: 'data#download'
View:
<%= link_to 'Download CSV', download_path(format: :csv) %>
Note: Obviously, I have no idea where you get your #data from, since it isn't specified in your question.
I am doing a CSV Export in Rails App.
The data is returned as a JSON array from the backend API.
But now I am getting error as "undefined method 'to_csv' for #Hash:0x007f149a5ff978" in the export_data method.
I have included require "csv" in the config/application.rb file, and did server restarts.
Not sure what I am doing wrong.
index.html.erb
<button class="btn btn-default">Export to CSV</button>
usage_controller(front end)
def export_data
rest_resource = RestClient::Resource.new( ENV['USAGE_METRICS_API'] + '/get_events_data', :verify_ssl => false )
request = rest_resource.get :Authorization => cookies.signed[:remember_token], :content_type => 'application/json'
#events_data = JSON.parse(request)
respond_to do |format|
format.html
format.csv { send_data #events_data.to_csv }
end
end
def self.to_csv
CSV.generate do |csv|
csv << column_names
all.each do |event|
csv << event.attributes.values_at(*column_names)
end
end
end
Your self.to_csv method doesn't make sense in the controller
You need to move to_csv to you model which will serialize the data into a csv
follow this tutorial to implement this: http://railscasts.com/episodes/362-exporting-csv-and-excel
I am trying to generate a CSV output with data from my database. I would like to provide these data to a third party, so I picture I would give to someone a URL (website.com/api_data/cars) and by accessing this URL the person would be able to work with it - I think I want to access the URL and then to see there (in the action) data displayed and separated by , or ;.
But how to do that?
So far, I am trying following approach:
csv_string = CSV.generate do |csv|
cols = ["column one", "column two", "column three"]
csv << cols
csv << ["A", "B", "C"]
#filename = "data-#{Time.now.to_date.to_s}.csv"
end
send_data(csv_string, :type => 'text/csv; charset=utf-8; header=present', :filename => #filename)
This is in the controller generate_data and action csv_cars.
When I run this action (webste.com/generate_data/csv_cars), it will automatically pop up a window to download the file.
But how to write the CSV content to the action? So when I open the URL, I'll see there written the content from the database?
I know this is an old thread but I came across it in my search so in case someone else does the same, here's my answer and what worked for me.
I think bhanu had a good way of going about it but I did change something. Instead of doing #cars within the respond_to, I just called send_data Cars.to_csv since, as Rob stated, it was made as a class method. It worked beautifully for me.
class Car < ActiveRecord::Base
def self.to_csv(make)
attributes = %w{id name price} #customize columns here
cars = Car.where(maker_name: make)
CSV.generate(headers: true) do |csv|
csv << attributes
cars.each do |car|
csv << attributes.map{ |attr| car.send(attr) }
end
end
end
end
And then in the controller
class CarsController < ApplicationController
def index
send_data Cars.to_csv('Chevy'), filename: "cars-#{Date.today}.csv"
end
end
I understand that this will be called when you go to cars/index but you can put that into any method, if statement or anything you want and just have it called whenever you would like from there. You can also have arguments, as I did above with make, and query for certain fields. It was definitely a lot easier than I thought it was going to be. Hope this helped someone.
You need to do something like this.
def csv_cars
headers = ['column one', 'column two', 'column three']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
csv << ["A", "B", "C"]
end
send_data csv_data, filename: "data-#{Date.today.to_s}.csv", disposition: :attachment
end
define a to_csv method in your model as shown below
class Car < ActiveRecord::Base
def self.to_csv
attributes = %w{id name price} #customize columns here
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |car|
csv << attributes.map{ |attr| car.send(attr) }
end
end
end
end
Later in your controller
class CarsController < ApplicationController
def index
#cars = Car.all
respond_to do |format|
format.html
format.csv { send_data #cars.to_csv, filename: "cars-#{Date.today}.csv" }
end
end
end