【Ruby】CSVをデータベースのように扱う

2017年8月17日Ruby

こんにちは、しきゆらです。

今回は、タイトルにある通りCSVに関するメモです。

 

時々触ることになるCSVですが、普通のDBでいいじゃん、って思いませんか?

私はそう思ってあまり触ってこなかったんですが、DBを適当に作ると後々変更しにくくなるということにようやく気付きました。

そこで、今回は忘れないうちに基本的なCSVの触り方をメモしておきます。

 


 

CSVとは?

Comma Separated Valueの略です。

そのまま、データの中身が以下のようにカンマ区切利となっているものです。

data1,data2,data3
hoge,fuga,piyo
1,2,3

特に難しいファイル形式ではなく、普通のテキストエディタで編集することも可能です。

Excelでひらけば、エクセルファイルのような感覚で操作できます。

 

RubyからCSVを扱う

なんと、便利なことに標準添付のライブラリがあります。

つかうには、いつも通り

require 'csv'

でOK。

CSVライブラリにあるメソッドなどは公式リファレンスを参照してください。

library csv – Ruby2.4.0リファレンスマニュアル

基本的には、HashやArrayと似たような感じで操作できます。

 

CSVファイルの読み込み

一旦全て読み込む方法と1行づつ処理していく方法があります。

# ファイル全体を読み込む
csv = CSV.read("/path/to/file.csv")

# 1行づつ
CSV.foreach("/path/to/file.csv") do |row|
  # 1行に対するなんらかの処理
end

特定の行を見つけるだけなら1行づつでもいいかもしれません。

 

読み込み時のオプション

上記の読み込みでは、各行のデータが配列として返って来ます。

単純なデータの場合は問題ないかもしれません。

しかし、1行に含まれるデータ数が多い場合は何番目のデータが何を表すのかを覚えていなければいけません。

これは面倒!

 

それをなんとかするために、CSVを読み込むときにオプションを指定してあげましょう。

オプションに関する詳しいことは、リファレンスのCSVのnewメソッドに書いてあります。

よく使うであろう部分をいくつかピックアップすると、

  • :headers
    • CSVファイルの1行目をその縦列の名前として扱うように指定
    • trueや:first_rowを渡すと良い
  • :header_converters
    • ヘッダーを有効にした時、アクセスするときに文字列ではなくシンボルなどにしたいときに使う
    • lambda式やProcを渡してあげればいい
  • :col_sep
    • CSVはカンマ区切りですが、カンマをデータの中で使っている場合は別のもので代用したい
    • そんな時は、これに分ける文字を渡すと良い

こんなところでしょうか。

どう渡すかというと、連想配列で渡してあげます。

csv = CSV.read("/path/to/file.csv", {headers: true})
# 同じこと
csv = CSV.read("/path/to/file.csv", headers: true)

たくさんのオプションを渡す時は、Hashの変数を作って渡すものを入れ、引数として渡すと見栄えがいいです。

options = {
  headers: true,
  encoding: Encoding::UTF_8
}
csv = CSV.read("/path/to/file.csv", options)

 

CSVをデータベースのように扱う

ようやく本題です。

データの検索、データの追加、データの削除のみ扱います。

 

データの検索

単純なwhere文は以下のような感じ。

# ヘッダーをシンボルに置き換える
header_converter = lambda {|h| h.to_sym}
csv_options = {
  headers: true, # headerあり
  header_converters: header_converter, # headerをシンボルに置き換え
  encoding: Encoding::UTF_8 # データはUTF-8
}
csv = CSV.read("/path/to/file.csv", csv_options)

# データの検索
result = csv.select{|row| row.to_h if row.field?("hoge")}

CSVの1行ごとに"hoge"というデータがあるかを調べ、データがある行を配列として返します。

rowはheaderを有効にしているとHashのように扱えますが、CSV::Rowというクラスのインスタンスなので、

普通に返してもあまり嬉しくありません。

とりあえず、上のコードではhashに変換して返しています。

 

データの追加

基本的にはファイルなので、アペンドモードで開き追加します。

CSV.open("/path/to/file.csv", "a") do |csv|
  csv.puts [data1, data2, ...]
end

データは配列で渡してあげます。

 

データの削除

若干面倒ですが、以下のコードでいけます。

csv_table = CSV.table("/path/to/file.csv")
csv_table.by_row! # 行ごとに見ていく
csv_table.delete_if{|row| row.firld?("hoge")} # 特定のデータを含む行を削除

CSV.open("/path/to/file.csv", "w") do |csv|
  csv_table.each do |row|
    csv.puts row
  end
end

やっていることは、まずはCSVをtableメソッドで表のような形で読み込むみます。

その後、特定の行を削除しファイルに上書きしています。

 

deleteやdelete_ifメソッドはRubyオブジェクトないから削除するだけであって実際のファイルから消すわけではないので注意。

 

 

とりあえず、これだけあれば基本的な操作には困らないはずです。

困った場合は、この記事へ追記したり別記事として投稿します。

 

今回はここまで。

おわり

Posted by しきゆら