RailsでCSVダウンロード機能を実装する
背景
RailsでCSVダウンロード機能を実装することになりました。すでに簡単で優れた事例はQiita上にたくさん上がっていて、このあたりの記事を見ればさらっと実装できたのですが、
仕事先で見つけたCSV出力機能はこれらよりもずっと複雑な作りをしていたので、「この仕組みはどうなっているんだ!」と思ったことを調べつつ、実装してみることにしました。基本的には、上記で紹介した2つ目の記事に近いです。
なお、Railsのバージョンは5.2.4.2
です。
controller
controllerは上記で紹介した記事とほとんど変わらないです。htmlとcsvでformatを分けて、csvのフォーマットでリクエストがあったときに、csvを作成するメソッドが呼び出されます。
class SomethingsController < ApplicationController
include SomeCsvModule # これから定義します
def index
@somethings = Something.all
respond_to do |format|
format.html
format.csv do
generate_csv(@somethings) # これから定義します
end
end
end
end
view
ビューは至ってシンプルで、format: :csv
を指定したボタンを設置するのみです^^。
= link_to "CSVダウンロード", somethings_path(format: :csv)
module
ビューとコントローラーがこれだけシンプルな秘密は、moduleにありました。様々なcontrollerでの共通メソッドなので、controllers/concerns/
内に共通のモジュールを定義しました。
module SomeCsvModule
extend ActiveSupport::Concern
def generate_csv(somethings)
filename = "情報一覧_#{Date.today}.csv"
set_csv_request_headers(filename)
bom = "\uFEFF" # 解説します(1)
self.response_body = Enumerator.new do |csv_data| # 解説します(2・3)
csv_data << bom
header = %i(id 名前 内容)
csv_data << header.to_csv # 解説します(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') # 解説します(5)
# ↓解説します(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
解説1: bom
BOMとは、Byte Order Mark(バイト・オーダー・マーク)
の略で、Unicode
で記載された文書の冒頭につく短い文字列です。この文字列には、文書のエンコーディング方式等が記載されています。
Excelの標準の文字コードはShift-JIS
で、WEBの世界はUTF-8
なので、WEBアプリでCSV出力したデータをExcelで開こうとすると、確実に文字化けしてしまいます。
文書の冒頭のbom
でこの文書の文字コードはUTF-8
であると伝えれば、文字化けを防げます。
このモジュールでは、bom = "\uFEFF"
と定義し、続くcsvデータ作成処理の冒頭で、データの一番初めにbom
を追加しています。
なお、この部分の解説は、こちらの2記事を参考に作成しています。
解説2: self.response_body
self.response_body
の部分については、調べたけれども、よくわかりませんでした・・・。ごめんなさい。ただ、この辺りの記事から推測すると...
Railsがレンダリングするビューを定義する部分がself.response_body
のようです。デフォルトではnil
で、self.response_body
に値を代入することで、レンダリングするビューを作成することができます。
解説3: Enumerator.new
Enumerator.new
は、下記の記事を参考に調べた限りでは「配列的な要素を作成する」メソッドのようです。
▼参考
- Ruby の Enumerator とたわむれる
上記のコードのこの部分は
self.response_body = Enumerator.new do |csv_data|
csv_data << bom
header = %i(id 名前 内容)
csv_data << header.to_csv
somethings.each do |some|
body = [
# 中略
]
csv_data << body.to_csv
end
end
これと同じでした。
csv_data = []
csv_data << bom
header = %i(id 名前 内容)
csv_data << header.to_csv
somethings.each do |some|
body = [
# 中略
]
csv_data << body.to_csv
end
self.response_body = csv_data
解説4: to_csv
to_csv
は、csvライブラリのメソッドで、配列をcsv形式に変換してくれるメソッドのようです。以下、こちらのドキュメントからの引用コードです。
require 'csv'
csv_string = ["CSV", "data"].to_csv # => "CSV,data"
csv_array = "CSV,String".parse_csv # => ["CSV", "String"]
解説5: キーワード引数
こちらのメソッドのcharset: 'UTF-8'
の部分はキーワード引数と言って、ハッシュ のように引数にキーを指定する書き方です。
def set_csv_request_headers(filename, charset: 'UTF-8')
▼詳しくはこちら
- Rubyのキーワード引数の使い方を現役エンジニアが解説【初心者向け】
このように指定することで、メソッド内ではcharset
のキーワードでUTF-8
という値を呼び出すことができます。
解説6: self.response.headers
最後に、こちらの部分ですが、
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'
レスポンスが返ってくる時の、レスポンスのヘッダーに追加する一連のオプションを指定しています。
オプション一覧は、こちらです。
それぞれ、簡単に解説すると、
-
Content-Type
...コンテンツのMIMEタイプを指定する。 -
Content-Disposition
...コンテンツがHTML(inline
)なのか、添付ファイル(attachment
)なのかを指定する。filename="ファイル名"
オプションで、名前を付けて保存するウィンドウを出すことができる(*) -
Content-Transfer-Encoding
...は、リクエストの文字列が、どんな仕組みでエンコーディングされているかを記載している(詳細はこちら)
...だそうです。
(*)の部分は、公式ドキュメントにはそう書いてあるのですが、今のところ自分の環境では成功していません。ごめんなさい。。。
まとめ
...以上となります!仕組みを理解するまでに色々な学習が必要でしたが、なんとか無事CSV出力機能を実装することができました^^
仕事で見かけたコードは、まだまだレスポンスヘッダーに色々なオプションがついていたり、共通化できる処理をまとめて汎用化できる形に整理したり、職人的な技が目白押しだったのですが、自分もまずはこの形を習得して、様々なオプションを使いこなせるようになろうと思います。
(まずは、これのテストが書けるようになりたいな...)
引き続き、精進あるのみです♪
Author And Source
この問題について(RailsでCSVダウンロード機能を実装する), 我々は、より多くの情報をここで見つけました https://qiita.com/tanutanu/items/d225fa5b1dd472b2f352著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .