We have decided to implement the CSV download function in Rails. There are already a lot of simple and excellent examples on Qiita, and if you look at the articles around here, you could easily implement it.
-[Easy 3 steps] How to output CSV with Rails -Active engineers explain how to output CSV with Rails [for beginners]
The CSV output function I found at work was much more complicated than these, so I decided to implement it while investigating what I thought "What is this mechanism!" .. Basically, it's close to the second article introduced above.
The Rails version is 5.2.4.2
.
controller
The controller is almost the same as the article introduced above. The format is divided into html and csv, and when there is a request in the csv format, the method to create csv is called.
controllers/somethings_controller.rb
class SomethingsController < ApplicationController
include SomeCsvModule #I will define it
def index
@somethings = Something.all
respond_to do |format|
format.html
format.csv do
generate_csv(@somethings) #I will define it
end
end
end
end
view
The view is very simple, just install a button with format:: csv
^^.
ruby:views/somethings/index.html.haml
= link_to "CSV download", somethings_path(format: :csv)
module
The secret behind the simple view and controller was in the module. Since it is a common method for various controllers, we have defined a common module in controllers / concerts /
.
/controllers/concerns/some_csv_module.rb
module SomeCsvModule
extend ActiveSupport::Concern
def generate_csv(somethings)
filename = "Information list_#{Date.today}.csv"
set_csv_request_headers(filename)
bom = "\uFEFF" #I will explain(1)
self.response_body = Enumerator.new do |csv_data| #I will explain(2.3)
csv_data << bom
header = %i(id name content)
csv_data << header.to_csv #I will explain(4)
somethings.each do |some|
body = [
some.id,
some.name,
some.content
]
csv_data << body.to_csv
end
end
end
def set_csv_request_headers(filename, charset: 'UTF-8') #I will explain(5)
#↓ I will explain(6)
self.response.headers['Content-Type'] ||= "text/csv; charset=#{charset}"
self.response.headers['Content-Disposition'] = "attachment;filename=#{ERB::Util.url_encode(filename)}"
self.response.headers['Content-Transfer-Encoding'] = 'binary'
end
end
BOM is an abbreviation for Byte Order Mark
, which is a short character string at the beginning of a document written in ʻUnicode`. This character string describes the encoding method of the document.
The standard character code of Excel is Shift-JIS
, and the world of WEB is ʻUTF-8`, so if you try to open the CSV output data with the WEB application in Excel, the characters will definitely be garbled.
You can prevent garbled characters by telling bom
at the beginning of the document that the character code of this document is ʻUTF-8`.
In this module, we define bom =" \ uFEFF "
and add bom
to the beginning of the data at the beginning of the subsequent csv data creation process.
In addition, the explanation of this part is created by referring to these two articles.
-[CSV output without garbled characters in UTF-8 with BOM](https://doruby.jp/users/ueki/entries/BOM%E4%BB%98%E3%81%8DUTF-8%E3%81%A7 % E6% 96% 87% E5% AD% 97% E5% 8C% 96% E3% 81% 91% E3% 81% 97% E3% 81% AA% E3% 81% 84CSV% E5% 87% BA% E5 % 8A% 9B) -BOM [Byte Order Mark] Byte Order Mark / Byte Order Mark
I checked the self.response_body
part, but I didn't understand it well ... sorry. However, guessing from the articles around here ...
-How Rails finds templates -Check how the method that does not specify render in rails specifies the render destination Try2
The part that defines the view that Rails renders seems to be self.response_body
. The default is nil
, and you can create a view to render by assigning a value to self.response_body
.
ʻEnumerator.new` seems to be a " create array element " method as far as I checked by referring to the following article.
▼ Reference -Play with Ruby Enumerator
This part of the above code
self.response_body = Enumerator.new do |csv_data|
csv_data << bom
header = %i(id name content)
csv_data << header.to_csv
somethings.each do |some|
body = [
#Omission
]
csv_data << body.to_csv
end
end
It was the same as this.
csv_data = []
csv_data << bom
header = %i(id name content)
csv_data << header.to_csv
somethings.each do |some|
body = [
#Omission
]
csv_data << body.to_csv
end
self.response_body = csv_data
to_csv
is a method of the csv library and seems to be a method that converts an array to csv format. Below is the quoted code from this document.
require 'csv'
csv_string = ["CSV", "data"].to_csv # => "CSV,data"
csv_array = "CSV,String".parse_csv # => ["CSV", "String"]
The charset:'UTF-8'
part of this method is called ** keyword argument **, which is a way to specify a key as an argument like a hash.
python
def set_csv_request_headers(filename, charset: 'UTF-8')
▼ Click here for details -Active engineers explain how to use Ruby keyword arguments [for beginners]
By specifying it like this, you can call the value ʻUTF-8 with the keyword
charset` in the method.
Finally, here is the part
python
self.response.headers['Content-Type'] ||= "text/csv; charset=#{charset}"
self.response.headers['Content-Disposition'] = "attachment;filename=#{ERB::Util.url_encode(filename)}"
self.response.headers['Content-Transfer-Encoding'] = 'binary'
Specifies a set of options to add to the response header when the response is returned. Click here for a list of options.
To briefly explain each,
-** Content-Type
** ... Specify the MIME type of the content.
-** Content-Disposition
** ... Specify whether the content is HTML (ʻinline) or attachment (ʻattachment
). With the filename =" filename "
option, you can bring up a window to save as (*)
-** Content-Transfer-Encoding
** ... describes how the character string of the request is encoded (see [here](https://wa3.i- for details]. 3-i.info/word11117.html)))
... it seems. The (*) part is written in the Official Document, but for now I am Has not been successful in this environment. sorry. .. ..
... that's it! It took a lot of learning to understand the mechanism, but I managed to implement the CSV output function ^^
▼ Created CSV output button: sunny:
The code I saw at work still had various options in the response header, organized the processes that could be shared into a generalized form, and was full of craftsmanship, but first of all, this I will learn the shape and master various options.
(First of all, I want to be able to write a test for this ...) I will continue to devote myself ♪
Recommended Posts