Implement CSV download function in Rails

background

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

Explanation 1: bom

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

Explanation 2: self.response_body

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.

Commentary 3: Enumerator.new

ʻ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

Explanation 4: to_csv

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"]

Explanation 5: Keyword arguments

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.

Commentary 6: self.response.headers

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.

--HTTP Header

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. .. ..

Summary

... 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: Image from Gyazo

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

Implement CSV download function in Rails
Implement application function in Rails
Implement follow function in Rails
[Rails] Implement search function
Implement markdown in Rails
[Rails] Implement User search function
Implement LTI authentication in Rails
Implement import process in Rails
[Rails] Implement image posting function
Implement post search function in Rails application (where method)
[Rails] Implement credit card registration / deletion function in PAY.JP
Implement user follow function in Rails (I use Ajax) ②
Implement user follow function in Rails (I use Ajax) ①
[Rails] Implementation of CSV import function
Add a search function in Rails.
[Rails] Implementation of CSV export function
Implement login function in Rails simply by name and password (1)
Implement login function in Rails simply by name and password (2)
Implement tagging function in form object
[Ruby on Rails] CSV output function
Implement PHP implode function in Java
Implement a contact form in Rails
Implement login function simply with name and password in Rails (3)
Implement user registration function and corporate registration function separately in Rails devise
[Rails] A simple way to implement a self-introduction function in your profile
I tried to implement Ajax processing of like function in Rails
Implement star evaluation function in Rails 6 (Webpacker is installed as standard)
How to implement search functionality in Rails
[Rails] Function restrictions in devise (login / logout)
Implemented follow function in Rails (Ajax communication)
How to implement ranking functionality in Rails
Implement button transitions using link_to in Rails
Rails CSV basics
[Rails 6] Ranking function
Implement Rails pagination
[Rails] Category function
Group_by in Rails
[Rails] Notification function
Create authentication function in Rails application using devise
Implement share button in Rails 6 without using Gem
Customize the output with Wagby's CSV download function
[Rails] Implementation of retweet function in SNS application
How to implement a like feature in Rails
Implement the Like feature in Ajax with Rails.
Implement iteration in View by rendering collection [Rails]
How to make a follow function in Rails
Simple notification function in Rails (only when followed)
How to implement image posting function using Active Storage in Ruby on Rails
How to implement a like feature in Ajax in Rails
Model association in Rails
Adding columns in Rails
Introduce devise in Rails to implement user management functionality
Disable turbolinks in Rails
[Rails] Implemented hashtag function
CSRF measures in Rails
[rails] tag ranking function
Implement Rails account BAN
[Ruby on Rails] Post image preview function in refile
I want to define a function in Rails Console
^, $ in Rails regular expression
Rails search function implementation