LoginSignup
2

More than 3 years have passed since last update.

よくあるデータ出力機能を二段階に分けて作ったら機能の汎用性がとても高くなったはなし

Last updated at Posted at 2019-12-06

この記事は食べログ Advent Calendar 2019 7日目の記事です。
よくあるデータ出力機能をふたつに分けて作ったら、機能の汎用性がとても高くなったはなしをします。

おそらくよくあるデータ出力処理

ふだん、データ出力ってどのように作りますか?

自分は、こんな感じで
レコードの絞り込みとデータの取得を同時に行なって
1レコードずつ出力したい順に情報を並べ替えてファイルに出力
することが多いです。

おそらくよくあるデータ出力実装.rb
records = Employee.select(:age, :first_name, :last_name).where(job: :engineer)
CSV.open("engineer.csv") do |csv|
  csv << [:first_name, :last_name, :age] # headers
  records.each do |record|
    csv << [record.first_name, record.last_name ,record.age]
  end
end

いつものようにしたけれど、コードが長くなっちゃった

その日は出力したい情報の種類が多いデータ出力を作っていました。
いつものように作ってみたけれど、データの取得先だけで5種類もあってコードがとても長い、、、
なんとかしたい。
そのときふと
出力したいレコードの絞り込みと、出力したいデータの取得処理を
分けてみたらどうだろうと思いました。

レコードの絞り込みと、データの取得をわけてみた

そうして出来上がったのがこちらです。

欲しいレコードの検索条件と
知りたいデータの項目名を定義したファイルを渡します。

id name tel address station url reviews_url yoyaku

するとまず、レコード絞り込み機能が
レコードの特定条件で絞り込み検索をして出力対象レコードのidのみを出力します。

id name tel address station url reviews_url yoyaku
100
200
300

続いて、このファイルがデータ取得機能に渡されて

id name tel address station url reviews_url yoyaku
100 カフェ 03-0000-0000 恵比寿南 恵比寿駅 /100/ /100/dtlrvwlst なし
200 ラーメン屋 03-0000-0000 恵比寿南 恵比寿駅 /200/ /200/dtlrvwlst なし
300 イタ飯屋 03-0000-0000 恵比寿南 恵比寿駅 /300/ /300/dtlrvwlst あり

idをキーにデータを取ってきて、項目名列に対応した情報を付加してくれます。

表の項目名は、データ取得先ごと色分けしてみました。
データ取得先はDBだったりAPIだったりするので、単純に結合もできず
特にAPIはデータを取るときにページングや並列処理をしたりもするのでわりと手間がかかります。

データ取得機能の汎用性がとてもよかった

長いコードを整理するために機能を分割したのですが
思いがけず、データ取得機能の汎用性がとても高いことに気づきました。

たとえば
レコードを絞り込む条件は違うけど、出力したいデータが似ている機能を新しく作りたいときは、
レコードの絞り込み部分だけ作れば、あとはデータ取得機能に渡すだけ!

あるいは
既にレコードは特定できていて、詳細情報を知りたいとき
id列を埋めたファイルをデータ取得機能に渡すだけ!

渡すだけ!これはなかなか汎用性があるぞう。

単純なデータ出力ではあまり恩恵はなさそうですが
今回ご紹介したように、データ出力のために用意する種類が多くて手間がかかるケースで
それを利用したいシーンが複数あると、たくさんの恩恵を受けられそうです。

ドメイン間は疎結合のまま、情報満載なデータ出力ができそうな予感

今回はひとつのドメインのデータ取得機能だけ作りましたが、別のドメインにもデータ取得機能を作り
知りたいデータの項目定義ファイルに、いろんなドメインのデータ取得機能を旅させることで
情報満載なCSVファイルを作ることもできそうです。

感想

ふだん何気なくやっていたことを少し変えてみたら新しい発見につながったので
今後も何気ないことを改めて考えてみて、いろんな発見をしていきたいですね!

明日は @hiroteru_ さんによる「UIViewの角丸と影のおはなし」です。
お楽しみに!!

最後までご覧いただきありがとうございました!

おまけ

データ出力機能を作るときに役立ちそうなコード集です。

最近のExcelってUTF-8を理解できるんですって(ただしBOMつきに限る)

最近のExcelは、UTF-8でもBOMつきであれば文字化けせずにcsvファイルを開いてくれるようです。

BOMつきUTF-8のCSVを作る
File.write(filepath, "\xEF\xBB\xBF") # excelで直接UTF-8を開けるようにするためにBOMをつける
CSV.open(filepath, 'a') do |csv|     # 追加書き込みモードで開く

これで「ファイルをインポート」機能とお別れできます。

この方法で作成したCSVを読み込むときは、BOMついてるかもよ。と教えてあげる必要があります。

BOMつきUTF-8のCSVを読み込む
CSV.open(filepath, encoding: "BOM|UTF-8") do |csv|

データ取得機能で活躍した遅延ロード

不必要なデータまで取得していると、処理に無駄な時間がかかってしまいます。
1つの取得先のデータだけあれば済むときに、5つの取得先からデータを取得したくないですよね。
こんなときに遅延ロードが大活躍しました。

class Hoge
  def initialize(id)
    @id = id
  end

  def name
    data.name
  end

  def tel
    data.tel
  end

  private

  def data
    return @data if defined? @data # メモ化
    @data = HogeData.select(:name, :tel).where(id: @id).take
  end
end

header = csv.headers # 出力したいと指定されている項目名を取得
-> ["name", "tel"]

hoge = Hoge.new(id)
header.each do |column|
  hoge.public_send(column.to_sym) # 初回にnameが呼び出されたときに、中のdataメソッドがデータを取得してくれる
  # 続いて tel が呼び出されたときは、メモ化された@dataを見るので改めてデータ取得はしない
end

このように、nameの情報を出力したい。と呼び出されたときにはじめてデータを取得することで不必要なデータは取得せずに済むようになりました。

あるクラスのメソッドをまとめてdelegateに定義する

delegateって明示的にメソッド名を定義する必要があって、面倒ですよね。
Hogeクラスのメソッド全部をdelegateしたいときは、こんな書き方で定義できました。

delegate *Hoge.instance_methods(false), to: :hoge

データ取得機能では、データの取得先毎にクラスをわけてオブジェクトを作っています。
メソッドが呼び出されたら、そのデータを持っているオブジェクトにdelegateするように、この方法で実現しています。

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
2