9
6

More than 3 years have passed since last update.

[rails] CSV非同期ダウンロードでやったこと

Last updated at Posted at 2019-12-25

大量データのCSVダウンロード

既存のやり方・問題


respond_to do |format|
  format.html
  format.csv do
    ~~~~~~
  end
end
  • ロードが長い
  • タイムアウトエラーになる

この2つを解決しようとして色々検索して試してみましたが、どれもうまく行かず。
さらには、大量データダウンロードの非同期処理はあんまりやってなさそう(?)

ということで、実際に試したことを書いていきます

解決策

定時処理でファイル作成-> S3にアップロード
リンクでS3のファイルをダウンロード

というやり方に行き着きました。

それまでに試したこと

非同期処理
挫折ポイント -> job で send_date が使えなかった。(ActionController のメソッド(?))
使い方はありそうだったが、うまく行かず断念。

実装

定時処理

CSVデータを作成する処理

jobs/file_upload_job.rb
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

+ファイルをアップロードする処理

jobs/file_upload_job.rb
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の設定等は省きますが、設定してください。

ダウンロードボタン

app/views/~~/index.html.haml
= link_to 'CSVダウンロード', csv_download_path

app/controllers/~~_controller.rb
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文後の処理が確認できなかったのが、難点。
  • ぜひご参考までに見ていただけたら〜〜〜
9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6