先日Railsで作成したCSVをS3にアップロードする機能を実装しました。
思っていたよりも考慮することが多かったので、内容をまとめておこうと思います。
環境
Rails 5.2.3
Ruby 2.6.3
前提
CSVをExcelで確認することは想定していない
※この前提は重要です。今回まとめた方法で出力されたcsvファイルはExcelで確認すると文字化けしたりして、期待した表示ができません。Excelで確認できるようにするには別途考慮が必要です。
gemの導入
RailsからAWSへアクセスするためのgemをインストールする必要があります。
Gemfileに以下を追加します。
gem 'aws-sdk', '~> 3'
共通クラスの作成
今回作成するCSV出力機能は共通ロジックとして使用したかったので、/app/lib
にoutput_csv.rb
というファイルを作成します。
output_csv.rbには以下のようにクラスを定義します。
このクラスのメソッドは全てクラスメソッドにします。
require 'csv'
require 'nkf'
class OutputCsv
class << self
# ここにメソッドを追加していく
end
end
※ requireしているcsvとnkfは後ほど使用します。
S3への出力メソッド実装
S3に出力するメソッドを作成します。
パラメータで受け取ったCSV文字列をS3に出力します。
CSV文字列作成部分は後述します。
# 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文字列に変換してファイル出力します。
# 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にしています。
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ファイルが出力されます。