💎

RubyにおけるCSVファイルの取り扱い方法

2022/11/18に公開

これはなに

Ruby on Railsを体系的に学ぶの続編。
CSVファイルを取り扱ってみた。

対象箇所はどこらへん

  • ファイルをs3などの外部へアップロードするのはuploaderで行う
  • CSV本体を操作するのはcontroller,workerに記述される事が多い
    • 同期処理ならcontroller
    • 非同期処理ならworker

CSVファイルの処理の流れ

ダウンロード

  1. ユーザーがCSVで取得したい情報でリクエストを投げる
  2. 空のCSVを新規作成する
  3. CSVファイルを開き書込みを行う
  4. 書き込みが完了したCSVファイルをレスポンスで返す

アップロード

  1. ユーザーがCSVファイルを作成し、CSVファイル情報のリクエストを投げる
  2. リクエストよりCSVファイル情報を取得する
  3. s3などの外部にアップロードする

CSVを使用して非同期でデータを更新する

  1. ユーザーがCSVファイルを作成し、CSVファイル情報のリクエストを投げる
  2. リクエストよりCSVファイル情報を取得する
  3. s3などの外部にアップロードする
  4. 非同期処理を開始する
  5. s3などの外部よりファイルをダウンロードする
  6. CSVファイルを開き、読み取る
  7. CSVファイルより取得した情報を使用して対象データを更新する
  8. CSVファイルを閉じる

ファイルを外部へアップロードするgem

  • ファイルを外部へアップロードする処理はgemを使用するのが一般的である
  • CarrierWave
  • Refile
    • CarrierWaveの後継だったが更新が途絶えているらしい...
  • Shrine

CSV本体を操作する

  • Rubyのライブラリには二種類ある
    • 組み込みライブラリ
      • Ruby本体に組み込まれているライブラリ
      • ファイルを取り扱うFileクラスは組み込み
    • 標準添付ライブラリ
      • Rubyに標準で実装してあるが明示的に呼び出してあげる必要がある
      • CSVならrequire "csv"
      • CSVファイルを取り扱うCSVはこちら
  • CSVを取り扱うにはFileクラスとCSVクラスの2種類がある

CSVを新規作成

require "csv"

text =<<-EOS
id,first name,last name,age
1,taro,tanaka,20
2,jiro,suzuki,18
3,ami,sato,19
4,yumi,adachi,21
EOS

csv = CSV.generate(text, headers: true) do |csv|
  csv.add_row(["5", "saburo", "kondo", "34"])
end
  • CSV.generateで本体を作成

  • 中身の書き込み方

    • textでCSVの形を作って追加する
    • csv.add_rowで1行ずつ追加する
  • 参考LINK

  • BOM付きCSVを作成する

    • BOMとは...CSVファイルにはバイトを前から読み込む方法と後ろから読み込む方法がある。その読み込み方をBOMに指定して付ける事で正しい順番で読んでくれるようになる。
    • CSVを作成する時に付ける
bom = "u\FEFF"
# このメソッドに与えられた文字列は変更されるので、新しい文字列オブジェクトが必要な場合は Object#dup で複製してください。
CSV.generate(bom.dup) do |csv|
    csv << ["1行目","hoge"]
end

CSVファイルを開き書込む

  • generateで作成する時に書き込むこともできるが、作成されているファイルに対して書き込む事も可能
  • ファイルに書き込む場合はopenの時にオプションを指定する
require "csv"

CSV.open("filepath", "ab") do |csv|
    csv << ["1行目","hoge"]
    csv << ["2行目","fuga"]
end
  • いくつかのオープンモードがある
    • "w"は元の中身を空にする
    • "a"は末尾に追加する
    • "b"フラグをつけるとバイナリモードでオープンすることができる(例:"ab")
      • 転送モードがアスキーモードなど、方法によって改行コードが自動で変わってしまうことがある
      • 変えさせないためにはバイナリモードでオープンするのが安全

バイナリモードは改行コード関係のとこかも?

CSVファイルを開き読み取る

Fileクラス

  • ブロックで囲むと、終了した時点でファイルをクローズしてくれる
    • 囲まない場合はfile.closeする必要がある
File.open("filepath") do |file|
    # 一行ずつ読み込む
    file.each_line do |line|
        puts line
    end
end
  • 読み込みのみだが1行ずつの読み込みをより簡単に書く方法
    • Openを書かなくても良い
File.foreach("filepath"){|line|
    puts line
}
  • 一気にファイルを読み込む場合
    • Openを書かなくても良い
File.read("filepath")

CSVクラス

  • CSVファイルを一行ずつ読み込む場合
require "csv"

CSV.foreach("filepath") do |row|
    puts row
end
  • 1行ずつ読み込むのを1行で記述する場合
require "csv"

CSV.foreach("filepath"){|row| puts row}
  • 一気に読み込む場合
require "csv"

puts CSV.read("filepath")
  • ファイルではなく文字列をそのまま処理する場合
require "csv"

csv_text = <<~CSV_TEXT
  Ruby,1995
  Rust,2010
CSV_TEXT

CSV.parse(csv_text) do |row|
  p row
end

Optionについて

  • 基本的にはCSV.newと同じoptionが指定できる
    • headers
      • 1行目をヘッダーとして扱える
    • skip_blanks
      • 空行を読み飛ばしてくれる
    • col_sep: "指定した区切り文字"
      • 指定した区切り文字をパースして区切ってくれる
    • encoding
      • 入出力が異なるCSVではエンコードを指定する必要がある
      • 左に外部エンコーディング(読み込み元ファイルの型)、右に内部エンコーディング(Rubyで読み込むファイルの型)を指定する
        require 'csv'
        # UTF-32BE な CSV ファイルを読み込んで UTF-8 な row をブロックに渡します
        CSV.foreach("a.csv", encoding: "UTF-32BE:UTF-8"){|row| p row }
        

最後に

  • CSVで一番難しいのはエンコーディングの制御らしい。今回は特に躓かなかったので割愛。また今度...。

Discussion