13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

マイナビAdvent Calendar 2019

Day 18

roo gemを使ってExcelからデータを読み込む

Last updated at Posted at 2019-12-17

こんにちは、マイナビでエンジニア兼マネージャーをしている柴垣と申します。
マイナビ学生の窓口というメディアを担当しています。

きっかけ

マイナビ学生の窓口というメディアは大学生に対してキャリアのきっかけを届ける記事がたくさんあります。
ある日ディレクターから、「記事のカテゴリを更新して」というチケットが届きました。
対象の記事はidで指定されていて、現在のカテゴリ、変更後のカテゴリが記載されていました。
その数、約5,000
最初は気づかずに、1行ずつ、手作業で、マッピング用のHashを作っていましたが、その数に気づいた時、これはあかん!となり、excelをcsvに変換してマッピング用のHashを作るtaskを作成しようと思っていました。
そこへ、あるアドバイスが
「xlsxなどから読み込んで処理するなら roo gem を使えば、ある程度自動化できそうです。」
ありがとうございます!

rooを使う

rooとは

https://github.com/roo-rb/roo
Excelやその他類似のファイルを読み込むためのgemです

インストール

githubのREADMEにあるようにGemfileに追記します

Gemfile
gem 'roo', '~>2.8.0'

bundle installします。
現時点では2.8.2がインストールされました

Excelファイルを読み込む

ではExcelファイルを読み込みます。読み込むExcelファイルをnewの引数に指定します。

load_excel.rake
excel = Roo::Excelx.new('Excelのファイルパス')

Sheetを指定する

Sheetの指定は先ほど作成したRoo::Excelxのインスタンスにsheetメソッドで指定します。

load_excel.rake
sheet = excel.sheet('Sheet名')

指定したカラムのデータを読み込む

Sheetを指定したら、データを読み込みます。
データを読み込むメソッドはいくつかあるようですが、わたしはparseメソッドがオススメです。
ヘッダーも取り除かれます(必要な場合はheaders:trueを指定)、取得したいカラムの文字列を指定するだけです。
ExcelのSheetが以下のようになっていたとします。

id old_category_name new_category_name
1 カテゴリー1 カテゴリー2
2 カテゴリー1 カテゴリー3

そのときの指定方法は以下のようになります。

load_excel.rake
rows = sheet.parse(id: 'id', old_cname: 'old_category_name', new_cname: 'new_category_name')

rowsを出力することで正しく読み込めていることがわかります

load_excel.rake
rows.each { |row| puts row.inspect }
=> {:id=>1, :old_cname=>"カテゴリー1", :new_cname=>"カテゴリー2"}
{:id=>2, :old_cname=>"カテゴリー1", :new_cname=>"カテゴリー3"}

とてもカンタンですね!

番外編

今回はカテゴリの更新ということで、oldとnewが同じ記事のIDは配列にまとめる必要がありました
例えば

id old_category_name new_category_name
1 カテゴリー1 カテゴリー2
2 カテゴリー1 カテゴリー3
3 カテゴリー1 カテゴリー2
4 カテゴリー1 カテゴリー2
5 カテゴリー1 カテゴリー3

となっていた場合、期待する結果は以下のようになります。

[
  {old_cname: 'カテゴリー1', new_cname: 'カテゴリー2', ids: [1, 3, 4]},
  {old_cname: 'カテゴリー1', new_cname: 'カテゴリー3', ids: [2, 5]}
]

最初は、Array#findを使って、old_cname, new_cnameが結果の配列に存在していたらidsに追加という処理にしていました。
すると、レビュー時にまた神の声が聞こえてきました。

基本的にはコンテナオブジェクト(ArrayやHashなど)はHashのキーにしない方が良いのですが、ここは Array#find ではなく〜(略)

教えていただいた内容はold_cnamenew_cnameの配列をHashのkeyにして、そのvalueにidを突っ込んでいけばよいということでした。

そのために、まずHashのデフォルト値を指定します

output = Hash.new{|h, k| h[k] = []}

こうすることで、デフォルト値が[]になりました
あとはもう突っ込んでいくだけです

rows.each do |row|
  output[[row[:old_cname], row[:new_cname]]] << row[:id]
end

ouptput
=> {["カテゴリー1", "カテゴリー2"]=>[1, 3, 4], ["カテゴリー1", "カテゴリー3"]=>[2, 5]}

結果がHashになったことにより、カテゴリーの取得は少し工夫が必要で

output.each.each do |(old_cname, new_cname), ids|
end

とすることでold_cname, new_cname, idsそれぞれの変数に代入ができます

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?