業務でCSVの整形処理を扱いました。
今後も割と扱うこと多いらしいので扱い方をざっくりまとめておきます。
実装手順に沿ってまとめたので以下に従って実装すればCSVを整形してDBにデータを保存できるようになるはずです。
前提
- ruby 2.3.1
- Rails4.2.3
- hamlからCSVを送信する
①CSVをビューから受け取る
Railsのビューファイルからフォームを送るとActionDispatch::UploadedFileオブジェクトとして送信されます。
CSVを整形するためにはこれをFileオブジェクトに変換する必要があります。
file = params[:file].tempfile
ActionDispatch::UploadedFileオブジェクトには tempfile というキーでFileオブジェクトが格納されており
params[:file].tempfile
とすることでFileオブジェクトに変換できます。
②CSV.forEachでCSVを1行ずつに分解
rubyのCSVクラスの持つ便利なメソッドに CSV.foreEach
というのがあります。
これを使うとCSVのデータを1行ずつに分解できます。
file = params[:file].tempfile
# ここから追記
CSV.forEach(file, header_row: true) do |row|
row
end
こんな感じで CSV.forEach
の中で各行に対して処理を行います。このとき、第一引数はFilleオブジェクトである必要があります。
①でFileオブジェクトに変換したのはこのためです。
ここでポイントになるのが header_row: true
というオプションで、これを使うとheader(列名)の値とrow(列に対応する値)
の部分をキーバリューの組として取り出せます。例えば
日付 | 気温 |
---|---|
2021/06/11 | 30℃ |
こんな感じのCSVがあれば以下のようなオブジェクトがブロック変数rowに代入されます。
<CSV::ROW '日付':'2021/06/11', '気温':'30℃' >
このオブジェクトの便利なところとして、キーを指定して値を取り出せます。
row = <CSV::ROW '日付':'2021/06/11', '気温':'30℃' >
row['日付']
=> '2021/06/11'
row['日付'] = '2021/06/10'
row['日付']
=> '2021/06/10'
次のステップでこれを利用します。
③各行のデータをハッシュに変換
各行のデータを取り出す各行のデータをハッシュに変換する。このとき上記で確認したCSV::ROWオブジェクトが活躍します。
キーを指定して値を取り出せるので全体的なイメージとして以下のようなメソッドを組みます。
def convert_csv_to_hash(row)
params = {
date: row['日付']
temperature: row['気音']
}
return params
end
# ここまで追記
file = params[:file].tempfile
CSV.forEach(file, header_row: true) do |row|
params = convert_csv_to_hash(row)
end
こうすることでCSV.forEachの各行をrailsで扱えるハッシュに変換できます。
④変換したハッシュを元にDBにデータを保存
ハッシュに変換できたらDBにデータを保存します。
def convert_csv_to_hash(row)
params = {
date: row['日付']
temperature: row['気音']
}
return params
end
file = params[:file].tempfile
CSV.forEach(file, header_row: true) do |row|
params = convert_csv_to_hash(row)
# 追加。Hogeは保存したいモデル名
Hoge.create(params)
end
⑤この処理を何かしらのクラスで行う
ここまでで処理自体は記述できたのでこれをコントローラーなりに記述しましょう。
僕が今回実装したものだとCSVを扱う専用のService層を作ってそこに記述しました。
⑥プラスアルファ…例外処理について
CSVからデータを保存する際は複数データを纏めて保存する仕様のため、保存に失敗した際に具体的に
どのデータの保存に失敗したのかを検知する仕組みがあったほうがいいと思います。
例えば今回僕は以下のような例外処理を組みました。
class ImportCSV
def convert_csv_to_hash(row)
params = {
date: row['日付']
temperature: row['気音']
}
return params
end
file = params[:file].tempfile
CSV.forEach(file, header_row: true).with_index(1) do |row, line_num|
params = convert_csv_to_hash(row)
# ここから追記
begin
Hoge.create(params)
rescue e
return "#{line_num}行目が不正です。エラー内容:#{e.message}"
end
end
end
あとはエラーメッセージをコントローラーで受け取ってビューに表示する、Slackやメールで通知するなどすればOK。
感想
CSVの組み込みメソッドだけでほとんど処理できたので思ったよりかんたんでした。
まだまだ使えるメソッドはたくさんあると思うので今後もっと深堀りしていきたいです。