こんにちは、マイナビでエンジニア兼マネージャーをしている柴垣と申します。
マイナビ学生の窓口というメディアを担当しています。
きっかけ
マイナビ学生の窓口というメディアは大学生に対してキャリアのきっかけを届ける記事がたくさんあります。
ある日ディレクターから、「記事のカテゴリを更新して」というチケットが届きました。
対象の記事は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に追記します
gem 'roo', '~>2.8.0'
bundle installします。
現時点では2.8.2がインストールされました
Excelファイルを読み込む
ではExcelファイルを読み込みます。読み込むExcelファイルをnew
の引数に指定します。
excel = Roo::Excelx.new('Excelのファイルパス')
Sheetを指定する
Sheetの指定は先ほど作成したRoo::Excelx
のインスタンスにsheet
メソッドで指定します。
sheet = excel.sheet('Sheet名')
指定したカラムのデータを読み込む
Sheetを指定したら、データを読み込みます。
データを読み込むメソッドはいくつかあるようですが、わたしはparse
メソッドがオススメです。
ヘッダーも取り除かれます(必要な場合はheaders:true
を指定)、取得したいカラムの文字列を指定するだけです。
ExcelのSheetが以下のようになっていたとします。
id | old_category_name | new_category_name |
---|---|---|
1 | カテゴリー1 | カテゴリー2 |
2 | カテゴリー1 | カテゴリー3 |
そのときの指定方法は以下のようになります。
rows = sheet.parse(id: 'id', old_cname: 'old_category_name', new_cname: 'new_category_name')
rowsを出力することで正しく読み込めていることがわかります
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_cname
とnew_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
それぞれの変数に代入ができます