#大量データのCSVダウンロード
既存のやり方・問題
respond_to do |format|
format.html
format.csv do
~~~~~~
end
end
- ロードが長い
- タイムアウトエラーになる
この2つを解決しようとして色々検索して試してみましたが、どれもうまく行かず。
さらには、大量データダウンロードの非同期処理はあんまりやってなさそう(?)
ということで、実際に試したことを書いていきます
##解決策
定時処理でファイル作成-> S3にアップロード
リンクでS3のファイルをダウンロード
というやり方に行き着きました。
####それまでに試したこと
非同期処理
挫折ポイント -> job で send_date
が使えなかった。(ActionController のメソッド(?))
使い方はありそうだったが、うまく行かず断念。
##実装
###定時処理
CSVデータを作成する処理
require 'csv'
require 'aws-sdk'
class FileUploadJob < ApplicationJob
def perform
csv_data = CSV.generate do |csv|
csv << ['クラス', '名前'] #CSVのヘッダーの当たる部分
User.each do |user|
csv << [user.classroom.name, user.name]
end
end
end
end
+ファイルをアップロードする処理
require 'csv'
require 'aws-sdk'
class FileUploadJob < ApplicationJob
def perform
csv_data = CSV.generate do |csv|
csv << ['クラス', '名前'] #CSVのヘッダーの当たる部分
User.each do |user|
csv << [user.classroom.name, user.name]
end
end
if Rails.env.development?
# ローカルだとS3の確認ができないので、tmp下に作成するようにして確認する
File.open(File.join(Rails.root, 'tmp', "#{Date.current.year}_#{Date.current.month}.csv"), 'w'){ |f| f << csv_data }
else
s3 = Aws::S3::Resource.new(client: s3_client)
bucket = s3.bucket(Settings.s3.bucket_name)
object = bucket.object("uploads/csv/#{Date.current.year}_#{Date.current.month}.csv")
object.put(body: csv_data, acl: 'public-read')
end
end
private
def s3_client
Aws::S3::Client.new(
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
secret_access_key: ENV['AWS_SECRET_KEY_ID'],
region: 'ap-northeast-1'
)
end
end
こんな感じになります。
ENV['AWS_ACCESS_KEY_ID']
ENV['AWS_SECRET_KEY_ID']
Settings.s3.bucket_name
bucket.object("uploads/csv/#{Date.current.year}_#{Date.current.month}.csv")
このあたりの定数やファイルのpathは、自分の環境に合わせてください。
schedule.rb
の設定等は省きますが、設定してください。
###ダウンロードボタン
= link_to 'CSVダウンロード', csv_download_path
def index; end
def csv_download
year = Date.current.year
month = Date.current.month
if Rails.env.development?
if File.exist?("#{Rails.root}/tmp/#{year}_#{month}.csv")
data = File.open(File.join(Rails.root, 'tmp', "#{year}_#{month}.csv"), 'r')
send_data data.read,
filename: "#{year}_#{month}.csv"
else
# ファイルが無いときの処理
end
else
s3 = Aws::S3::Resource.new(client: s3_client)
object = s3.bucket(Settings.s3.bucket_name).object("uploads/csv/#{year}_#{month}.csv")
if object.present?
send_data object.get.body.read,
filename: "#{year}_#{month}.csv"
else
# ファイルが無いときの処理
end
end
end
private
def s3_client
Aws::S3::Client.new(
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
secret_access_key: ENV['AWS_SECRET_KEY_ID'],
region: 'ap-northeast-1'
)
end
こんな感じで行けると思います。
予期するファイル名があればダウンロードするといった感じです。
rescue Aws::S3::Errors::NoSuchKey => e
flash[:notice] = "ファイルがありません。"
redirect_to root_path
のようなコードも足しておくと良さそうです。
##まとめ
- 大量CSVダウンロードでタイムアウトエラーから始まり、それを回避するためのコードを使用してもダウンロードニ時間がかかるところから今回の作業をしました。
- 記事であまりCSV非同期ダウンロードがなかった。(自分のやりたいことに似ているもの)
- 非同期ダウンロードとは違う形になっているかもしれないですが、結果的には、リンクを踏んで非同期でダウンロードするよりもダウンロードのスピードは上がっていると思うので、良いかなと思います。
- CSVの勉強とS3へのアップロード・ダウンロードを勉強できてよき。
- ただ、ローカルだと
else
文後の処理が確認できなかったのが、難点。 - ぜひご参考までに見ていただけたら〜〜〜