[Rails]ActiveAdminでCSVダウンロード機能をカスタマイズする
ActiveAdminにはcsvダウンロード機能がデフォルトついているのですが、デフォルトのままだと一括でcsvデータを取得することができません。(管理画面表示1ページあたりの表示件数分になるので30件しか取得できませんでした)
また、csvの項目もデフォルトだとDBのカラム名のままになるので、管理画面上で表示項目をカスタマイズしている場合はcsvもカスタマイズする必要があります。
カスタマイズ方法を調べてみたところ方法は以下の2通りがありました。
既存のcsvダウンロード機能をカスタマイズする方法
csvダウンロード用のアクションを新たに作成する方法
両方試してみたのですが、csvダウンロード後にExcelで開きたい場合は2.の方法がおすすめです。
1.の場合はcsvファイルは文字コードがUTF-8で出力されるため、csvダウンロード後に文字コードをShift-JISに変換する必要があります。
1. 既存のcsvダウンロード機能をカスタマイズ
ActiveAdmin.register Post do
# csvダウンロード時のみ1ページの取得件数を10000件に変更
before_filter only: :index do
@per_page = 10_000 if request.format == 'text/csv'
end
# csvの内容をカスタマイズ
csv :force_quotes => true do
column :id
column :title
column("Author") { |post| post.author.full_name }
end
end
2. csvダウンロード用のアクションを新たに作成
ActiveAdmin.register Post do
# csvダウンロードアクションを作成
collection_action :download_report, :method => :get do
posts = Post.order('created_at DESC')
csv = CSV.generate do |csv|
csv << ['id', 'title', 'Author']
posts.each do |post|
csv << [
post.id,
post.title,
post.author.full_name
]
end
end
# Shift-JISでcsvを出力
send_data csv.encode('Shift_JIS', :invalid => :replace, :undef => :replace), type: 'text/csv; charset=shift_jis; header=present', disposition: "attachment; filename=report.csv"
end
# csvダウンロードリンクを追加
action_item(:csv_report, only: :index) do
link_to('csv report', params.merge(:action => :download_report))
end
# 既存のcsvダウンロードリンクを非表示にしておく
index :download_links => false do
# indexに表示する項目を記述
end
end
変換できない文字は「?」に置換するようにしています。
2015/2/9追記
データ数が多い場合はeachではなく、find_eachやfind_in_batchesを用いて分割して処理するようにしましょう。
ただし、orderは使えなくなるのでその点は注意が必要です。(idの昇順になります)
ActiveAdmin.register Post do
# csvダウンロードアクションを作成
collection_action :download_report, :method => :get do
csv = CSV.generate( encoding: 'SJIS' ) do |csv|
csv << ['id', 'title', 'Author']
Post.find_each(batch_size: 500) do |post| # 500件ずつ分割で取得
csv << [
post.id,
post.title,
post.author.full_name
]
end
end
# Shift-JISでcsvを出力
send_data csv.encode('Shift_JIS', :invalid => :replace, :undef => :replace), type: 'text/csv; charset=shift_jis; header=present', disposition: "attachment; filename=report.csv"
end
# csvダウンロードリンクを追加
action_item(:csv_report, only: :index) do
link_to('csv report', params.merge(:action => :download_report))
end
# 既存のcsvダウンロードリンクを非表示にしておく
index :download_links => false do
# indexに表示する項目を記述
end
end
2015/6/25追記
データ件数が10万件を超えたあたりで、上記の対応ではunicornのタイムアウトが発生してしまったため、collection_actionの部分をストリーミングでcsvをダウンロードするように変更しました。
- csvのエンコード変更メソッドを持つCsvUtilを追加
# app/admin/concerns/csv_util.rb
module CsvUtil
def self.encode_sjis(data)
data.encode('Shift_JIS', :invalid => :replace, :undef => :replace)
end
end
- collection_actionの修正
ActiveAdmin.register Post do
collection_action :download_report, :method => :get do
self.response.headers["Content-Type"] ||= 'text/csv; charset=Shift_JIS'
self.response.headers["Content-Disposition"] = "attachment;filename=report.csv"
self.response.headers["Content-Transfer-Encoding"] = "binary"
self.response.headers["Last-Modified"] = Time.now.ctime.to_s
self.response_body = Enumerator.new do |yielder|
yielder << CsvUtil.encode_sjis(['id', 'title', 'Author'].to_csv)
posts.find_each(batch_size: 500) do |post| # 500件ずつ分割で取得
yielder << CsvUtil.encode_sjis([
post.id,
post.title,
post.author.full_name
].to_csv)
end
end
end
action_item(:csv_report, only: :index) do
link_to('csv report', params.merge(:action => :download_report))
end
end
- unicorn.rbのtimeout時間を修正
# config/unicorn.rb
timeout 120
データが巨大な場合は120秒でもタイムアウトするので、適宜調整してください。
これで先にレスポンスを返して、残りのデータをストリーミングでダウンロードするようになりタイムアウト問題を解消できました。