LoginSignup
17
13

More than 3 years have passed since last update.

Railsで作成したCSVをS3に出力する方法

Posted at

先日Railsで作成したCSVをS3にアップロードする機能を実装しました。
思っていたよりも考慮することが多かったので、内容をまとめておこうと思います。

環境

Rails 5.2.3
Ruby 2.6.3

前提

CSVをExcelで確認することは想定していない

※この前提は重要です。今回まとめた方法で出力されたcsvファイルはExcelで確認すると文字化けしたりして、期待した表示ができません。Excelで確認できるようにするには別途考慮が必要です。

gemの導入

RailsからAWSへアクセスするためのgemをインストールする必要があります。
Gemfileに以下を追加します。

Gemfile
gem 'aws-sdk', '~> 3'

共通クラスの作成

今回作成するCSV出力機能は共通ロジックとして使用したかったので、/app/liboutput_csv.rbというファイルを作成します。
output_csv.rbには以下のようにクラスを定義します。
このクラスのメソッドは全てクラスメソッドにします。

app/lib/output_csv.rb
require 'csv'
require 'nkf'

class OutputCsv
  class << self
    # ここにメソッドを追加していく
  end
end

※ requireしているcsvとnkfは後ほど使用します。

S3への出力メソッド実装

S3に出力するメソッドを作成します。
パラメータで受け取ったCSV文字列をS3に出力します。
CSV文字列作成部分は後述します。

app/lib/output_csv.rb
# CSV出力メソッド -- ①
def save_csv(data)
  # ファイル名は 'ランダム文字列_タイムスタンプ.csv' とする
  file_name = "#{SecureRandom.urlsafe_base64(9)}_#{Time.current.strftime('%Y%m%d%H%M%S')}.csv"
  # バケット直下のcsvという名前のフォルダ配下に出力する
  file_full_path = "csv/#{file_name}"

  # 開発環境ではローカルに、本番環境ではS3にCSV出力する -- ②
  if Rails.env.development?
    File.open(Rails.root.join('tmp', file_name), 'w') do |f|
      # NKFを使用して文字コードを変換 -- ③
      f.puts(NKF.nkf('-x -w', data))
    end
  else
    # S3クライアントのインスタンス作成 -- ④
    s3 = Aws::S3::Client.new
    # S3にCSVを出力 -- ⑤
    s3.put_object(bucket: ENV['AWS_S3_BUCKET'],
                  key: file_full_path,
                  body: NKF.nkf('-x -w', data),
                  content_type: 'text/csv')
  end
end

① CSV出力メソッドの定義です。パラメータ(data)で出力するCSV文字列を受け取ります。

def save_csv(data)

② 今回は開発環境ではローカルに、本番環境ではS3に出力するようにしています。開発環境でもS3に出力したい場合、この分岐は不要です。

NKFを使用して文字列を変換しています。

f.puts(NKF.nkf('-x -w', data))

第1引数の-xと-wは以下のような変換を指定しています。
-x: 半角カタカナを全角カタカナに変換せずに出力
-w: utf-8で出力

④ S3クライアントのインスタンスを作成しています。これを使用してS3にアクセスします。

s3 = Aws::S3::Client.new

注意点として、ここではコンストラクタにパラメータを渡していません。
理由はこのRailsアプリケーションが動作するEC2インスタンスのロールにS3へ書き込みをする権限が付与されているため、権限について考慮する必要がないためです。

ロールではなく、IAMユーザーの権限でS3にアクセスしたい場合は、以下のようにアクセスキーとシークレットキーを指定してあげる必要があります。

s3 = Aws::S3::Client.new(
  access_key_id: 'your_access_key_id',
  secret_access_key: 'your_secret_access_key'
)

⑤ S3にCSVファイルを作成します。

s3.put_object(bucket: ENV['AWS_S3_BUCKET'],
              key: file_full_path,
              body: NKF.nkf('-x -w', data),
              content_type: 'text/csv')

put_objectというメソッドを使用しています。
各パラメータの内容は以下の通りです。

・bucket: 出力するバケットの名称です。ここでは環境変数AWS_S3_BUCKETに設定されている想定で記述しています。
・key: 出力するディレクトリ名 + ファイル名を指定します。
・body: 上記で説明したNKFで変換した文字列を出力します。
・content_type: ファイル形式を指定しています。ここで明示的にcsvファイルであることを指定しないとS3上でCSVファイルとして認識されませんでした。

CSV文字列の作成

CSV文字列を作成し、その作成した文字列を上記で作成したsave_csvメソッドに渡してCSVファイルを出力するメソッドを作成します。

作成するメソッドでは、パラメータでヘッダー項目、データ項目を受け取り、それをCSV文字列に変換してファイル出力します。

app/lib/output_csv.rb
# CSV文字列作成メソッド -- ①
def execute(headers, values)
  output = CSV.generate do |csv|
    # ヘッダー出力 -- ②
    csv << headers
    # データ項目の出力 -- ③
    values.each do |value|
      csv << value
    end
  end

  # CSVファイル出力 -- ④
  save_csv(output)
end

① CSV文字列作成メソッドの定義です。
パラメータでヘッダーとデータ項目の配列を受け取ります。
例えば、以下のようなCSVを作成したい場合

id,name,age
1,hoge,20
2,fuga,31
3,foo,43

以下のような配列を作成してパラメータに渡します。

# ヘッダー
headers = ['id', 'name','age']
# データ項目
values = []
values.push([1, 'hoge', 20])
values.push([2, 'fuga', 31])
values.push([3, 'foo', 43])

OutputCsv.execute(headers, values)

② パラメータで受け取ったヘッダーの値をCSVにセットしています。

③ パラメータで受け取ったデータ項目をCSVにセットしています。配列になっているため、1行ずつ取り出してセットしています。

④ 上記で作成したsave_csvメソッドに作成したCSV文字列を渡してS3に出力します。

全コード

全てのコードは以下の通りです。
save_csvメソッドは外部から呼び出す想定はないため、privateにしています。

app/lib/output_csv.rb
require 'csv'
require 'nkf'

class OutputCsv
  class << self
    def execute(headers, values)
      output = CSV.generate do |csv|
        # ヘッダー出力
        csv << headers
        # データ項目の出力
        values.each do |value|
          csv << value
        end
      end

      # CSVファイル出力
      save_csv(output)
    end

    private

    def save_csv(data)
      # ファイル名は 'ランダム文字列_タイムスタンプ.csv' とする
      file_name = "#{SecureRandom.urlsafe_base64(9)}_#{Time.current.strftime('%Y%m%d%H%M%S')}.csv"
      # バケット直下のcsvという名前のフォルダ配下に出力する
      file_full_path = "csv/#{file_name}"

      # 開発環境ではローカルに、本番環境ではS3にCSV出力する
      if Rails.env.development?
        File.open(Rails.root.join('tmp', file_name), 'w') do |f|
          # NKFを使用して文字コードを変換
          f.puts(NKF.nkf('-x -w', data))
        end
      else
        # S3クライアントのインスタンス作成
        s3 = Aws::S3::Client.new
        # S3にCSVを出力
        s3.put_object(bucket: ENV['AWS_S3_BUCKET'],
                      key: file_full_path,
                      body: NKF.nkf('-x -w', data),
                      content_type: 'text/csv')
      end
    end
  end
end

使用方法

CSVファイル出力

S3にファイルを出力します。

headers = ['id', 'name','age']
values = []
values.push([1, 'あいうえお', 20])
values.push([2, 'かきくけこ', 31])
values.push([3, 'さしすせそ', 43])

OutputCsv.execute(headers, values)

S3に出力されたファイルを確認

指定したバケット、ディレクトリに以下の通りCSVファイルが出力されます。

スクリーンショット 2020-09-18 16.26.03.png

ファイルの中身を確認

S3に出力されたファイルをダウンロードして内容を確認すると以下のようになります。
スクリーンショット 2020-09-18 16.30.33.png

17
13
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
17
13