[Ruby]ymlファイルに定義されたCSV(TSV)の読み込み処理を行う

2016.03.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。Yuraです。
前回に引き続き、Rubyネタです。
今回はRailsは使用しませんので、ご了承ください。

はじめに

Ruby学習の一環として、Rubyのyamlライブラリとcsvライブラリを使い、
ymlファイルに定義されているCSVの一覧から中身を読み取るプログラムを書いてみました。

環境

・OS Windows8.1 64bit
・Ruby 2.2.4p230
・DB MySQL5.7

実装

読込対象ファイル

まず、ymlファイルに読み込みたいファイル名を記述します。
私はこんな感じで作りました。

csvList.yml

csv_folder:  ./csv/  #CSV置場のディレクトリ

csv_files:
- test.csv  #読み込みたいcsv名
- test.tsv  #読み込みたいtsv名

CSVファイルは、テスト用にサンプル売上データを作りました。
今回はcsvファイルだけでなく、tsvファイルも読み込んでみるので、
tsvファイルも用意します。

test.csv

sales_no,shop_cd,customer_cd,amount,sales_date
SAL0000001,SHOP001,CUST001,120000,2016/03/01
SAL0000002,SHOP001,CUST001,230000,2016/03/02
SAL0000003,SHOP001,CUST002,340000,2016/03/02
SAL0000004,SHOP001,CUST002,450000,2016/03/02
SAL0000005,SHOP002,CUST030,560000,2016/03/04

test.tsv

sales_no	shop_cd	customer_cd	amount	sales_date
SAL0000006	SHOP003	CUST100	980000	2016/03/01
SAL0000007	SHOP003	CUST100	760000	2016/03/02
SAL0000008	SHOP003	CUST100	540000	2016/03/02
SAL0000009	SHOP003	CUST100	320000	2016/03/02
SAL0000010	SHOP003	CUST100	100000	2016/03/04

ソースコード

実装したRubyのソースは以下の通りです。
Readerクラスを作成しました。
get_pathsメソッドでymlファイルから読込対象のCSVのパスを取得し、
readメソッドでCSVの中身を読み出します。
tsvファイルも、区切り文字にタブを指定することによって、CSVと同じように扱えます。

reader.rb

class Reader

  # ymlファイルに定義されたCSVのパスを取得
  def self.get_paths
    yaml = YAML.load_file("csvList.yml")
    csv_folder = yaml["csv_folder"]
    csv_files = yaml["csv_files"]
    csv_paths = Array.new
    csv_files.each do |file|
      csv_path = File.expand_path(csv_folder + file)
      csv_paths.push(csv_path)
    end
    return csv_paths
  end

  # CSV(TSV)ファイルの読み込みを行い、内容を返す
  def self.read(path)
    if File.extname(path) == ".csv"
      return CSV.read(path)
    elsif File.extname(path) == ".tsv"
      return CSV.read(path, col_sep: "\t")
    end
  end

end

使ってみる

実際に動かしてみます。
動かすといっても、CSVの中身を標準出力に出すだけですが。

paths = Reader.get_paths
paths.each do |path|
  p Reader.read(path)
end

・実行結果

[["sales_no", "shop_cd", "customer_cd", "amount", "sales_date"], ["SAL0000001","SHOP001", "CUST001", "120000", "2016/03/01"]
, ["SAL0000002", "SHOP001", "CUST001", "230000", "2016/03/02"], ["SAL0000003", "SHOP001", "CUST002", "340000", "2016/03/02"]
, ["SAL0000004", "SHOP001", "CUST002", "450000", "2016/03/02"], ["SAL0000005", "SHOP002", "CUST030", "560000", "2016/03/04"]]
[["sales_no", "shop_cd", "customer_cd", "amount", "sales_date"], ["SAL0000006","SHOP003", "CUST100", "980000", "2016/03/01"]
, ["SAL0000007", "SHOP003", "CUST100", "760000", "2016/03/02"], ["SAL0000008", "SHOP003", "CUST100", "540000", "2016/03/02"]
, ["SAL0000009", "SHOP003", "CUST100", "320000", "2016/03/02"], ["SAL0000010", "SHOP003", "CUST100", "100000", "2016/03/04"]]

ちゃんと読み込めることが確認できました。
せっかくなので、読み取ったデータをDBに突っ込んでみます。
DBに登録するにあたり、CSVの1行目ヘッダーの部分が邪魔なのでReaderクラスのreadメソッドを以下のように変更します。

  def self.read(path)
    if File.extname(path) == ".csv"
      return CSV.read(path, headers: :first_row )
    elsif File.extname(path) == ".tsv"
      return CSV.read(path, headers: :first_row, col_sep: "\t")
    end
  end

実行コードは次のようにして実行してみます。

paths = Reader.get_paths
paths.each do |path|
  Reader.read(path).each do |row|
    p row
  end
end

・実行結果

#<CSV::Row "sales_no":"SAL0000001" "shop_cd":"SHOP001" "customer_cd":"CUST001" "amount":"120000" "sales_date":"2016/03/01">
#<CSV::Row "sales_no":"SAL0000002" "shop_cd":"SHOP001" "customer_cd":"CUST001" "amount":"230000" "sales_date":"2016/03/02">
#<CSV::Row "sales_no":"SAL0000003" "shop_cd":"SHOP001" "customer_cd":"CUST002" "amount":"340000" "sales_date":"2016/03/02">
#<CSV::Row "sales_no":"SAL0000004" "shop_cd":"SHOP001" "customer_cd":"CUST002" "amount":"450000" "sales_date":"2016/03/02">
#<CSV::Row "sales_no":"SAL0000005" "shop_cd":"SHOP002" "customer_cd":"CUST030" "amount":"560000" "sales_date":"2016/03/04">
#<CSV::Row "sales_no":"SAL0000006" "shop_cd":"SHOP003" "customer_cd":"CUST100" "amount":"980000" "sales_date":"2016/03/01">
#<CSV::Row "sales_no":"SAL0000007" "shop_cd":"SHOP003" "customer_cd":"CUST100" "amount":"760000" "sales_date":"2016/03/02">
#<CSV::Row "sales_no":"SAL0000008" "shop_cd":"SHOP003" "customer_cd":"CUST100" "amount":"540000" "sales_date":"2016/03/02">
#<CSV::Row "sales_no":"SAL0000009" "shop_cd":"SHOP003" "customer_cd":"CUST100" "amount":"320000" "sales_date":"2016/03/02">
#<CSV::Row "sales_no":"SAL0000010" "shop_cd":"SHOP003" "customer_cd":"CUST100" "amount":"100000" "sales_date":"2016/03/04">

CSV.readにheaders: :first_rowオプションを追加すると、ヘッダーの値をキーとしたハッシュのような形にして返してくれました。
あとは、DBにデータを入れる処理を追加します。
MySQLの接続にmysql2を使うので、まだインストールしていない場合、下記コマンドでインストールしておきます。

>gem install mysql2
require 'mysql2'
paths = Reader.get_paths
client = Mysql2::Client.new(host: "localhost", username: "username", password: "password", database: "development_db")
paths.each do |path|
  Reader.read(path).each do |row|
    sales_no = row["sales_no"]
    shop_cd = row["shop_cd"]
    customer_cd = row["customer_cd"]
    amount = row["amount"]
    sales_date = row["sales_date"]
    client.query("INSERT INTO sales (sales_no, shop_cd, customer_cd, amount, sales_date) VALUES ('#{sales_no}','#{shop_cd}','#{customer_cd}','#{amount}','#{sales_date}')")
  end
end
client.close

実行後、salesテーブルにデータが入っていることを確認します。

おわりに

CSVファイルの読み込みは使う機会も色々と多いと思いますので、覚えておいて損はないはずです。
rubyのリファレンスマニュアルを見てもcsvライブラリも何やら奥が深そうなので、もう少し簡単に書く方法もありそうです。