35
24

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 5 years have passed since last update.

POROと大人になった僕

Last updated at Posted at 2018-12-10

食べログでマイクロサービス化チームに所属しています@itumeです。

この記事は 食べログAdventCalendar の11日目の投稿です。

最初に断っておきますと、オチのない話です。
Model/Controller/Viewのどこかに押し込めるとむずむずするものを、どうやって整理したら心地よいのか考え続けたTry&Errorの履歴の一部です。

POROについて

PORO、って誰が使い始めた言葉なのか知らないのですが、Plain Old Ruby Object、ActiveRecordなどを継承していないすっぴんのRubyオブジェクトのことと理解しています。

最初に作ったPOROの話

↓のようなCSVExcelファイル(どこかから手に入れたのか自分で作ったのか...)を画面からアップロードしたら、種類ごとに分けてtech_books, comics, magazinesテーブルに分けて保存したいという案件がありました。

|No.|種類|タイトル|巻|号
|---|---|---|---|---|---|
|1|技術書|たのしいRuby|""|""|
|2|マンガ|逆境ナイン|1|""|
|3|雑誌|Software Design|""|2018年10月号|

「この要件、この設計、ツッコミどころしかないな。。。」と感じられると思いますが、元ネタのコードを元ネタがわからないように改変してたらなんかこうなってしまいましたスイマセン。

この謎のCSVExcelファイルを適切にさばいて、適切なテーブルに保存するためのロジックをTechBook/Comic/Magazineのどれかに持ってもらうのはむずむずするので、謎のCSVExcelファイルをリソースとしていい感じにさばいてくれるPOROを作りました

app/models/mysterious_csv.rb
class MysteriousCsv

  class << self
    def insert
      MysteriousCsv::Parser.new(CSV.read("mysterious.csv")).mysterious_data.map(&:save)
    end
  end

  class Parser

    attr_reader :mysterious_data

    def initialize(data_arr)
      @mysterious_data = parse(data_arr)
    end

    private

    def parse(data_arr)
      data_arr.map do |data|
        case data[1]
        when "技術書"
          TechBook.new(title: data[2])
        when "マンガ"
          Comic.new(title: data[2], volume: data[3])
        when "雑誌"
          Magazine.new(title: data[2], number: data[4])
        end
      end
    end
  end
end

このinsertメソッドは非同期workerの処理の中で呼ばれます。
本物はtransactionかけてたりバリデーションエラー吐かせたり、他の処理からMysteriousCsvのインスタンス自体も使っていたりするのですが、要点だけかいつまむとこんな感じのものを作りました。
元ネタとなったコードは今でも使われていて、元気に動いておりますし、謎のCSVExcelファイルを取り込んでsaveする処理をTechBookに押し込めたりしなくて済んだので、まあ、よかったんじゃないのかなあと思っています。

ViewObjectの話

最初はこういうシンプルなControllerだったのに

app/controllers/hoge_controller.rb
class HogeController < ActionControllerBase
  def show
    @hoge = Hoge.find(params[:id])
  end
end

view側で@hogeだけでは判定できない表示制御をしたくて、こういう感じに育ったようです。

app/controllers/hoge_controller.rb
class HogeController < ActionControllerBase
  def show
    @hoge = Hoge.find(params[:id])
    @fuga_flg = # なにかの処理によって作られたboolean
    @today = Date.today
  end
end

Controllerがこういうノリだったので、view側はこういうノリで育ちました。

app/views/hoge/show.html.erb
<%= @hoge.some_attr_1 if @fuga_flg %>
<%= @hoge.some_attr_2 if @today.day < 15  %>
<%= render partial "some_partial" locals: {hoge: @hoge}  if @hoge.some_flg && @fuga_flg && @today.day < 15 %>

既に嫌な感じですが、このノリのまま育ち続けると「どういう条件のときに何が表示されるのかよくわからん!」という感じで大きくなります。
viewに複雑な条件分岐があるのも嫌だし、表示内容に対して誰が責任を持っているのかわからない実装を増やしたくなかったで、ViewObjectを試してみることにしました。

app/view_objects/hoge/show.rb
class ViewObjects::Hoge::Show

  attr_reader :hoge, :some_attr_1, :some_attr_2

  def initialize(hoge)
    @hoge = hoge
    @fuga_flg = # なにかの処理によって作られたboolean
    @today = Date.today
    @some_attr_1 = hoge.some_attr_1 if @fuga_flg
    @some_attr_2 = hoge.some_attr_2 if early_part_of_month?
  end

  def can_special_offer_week_display?
    @hoge.some_flg && @fuga_flg && early_part_of_month?
  end

  def early_part_of_month?
    @today.day < 15
  end
end
app/controllers/hoge_controller.rb
class HogeController < ActionControllerBase
  def show
    hoge = Hoge.find(params[:id])
    @view_obj = ViewObjects::Hoge::Show(hoge)
  end
end
app/views/hoge/show.html.erb
<%= @view_obj.some_attr_1 %>
<%= @view_obj.some_attr_2 %>
<%= render partial "some_partial" locals: {hoge: @view_obj.hoge}  if @view_obj.can_special_offer_week_display? %>

これは賛否両論ありました。自分としては

  • 謎の条件分岐に名前をつけられた
  • 「複雑な表示制御」自体の単体テストが書けた(RSpec)
  • viewで使えるもの = view_objectのattr_readerなので何を使っているのかが明確

という良いところがあると思っていたのですが

  • view_objectに対する共通認識を形成していなかったので「何このview_objectって」と物議を醸した
  • app/view_objects/hoge/show.rb だけぽつんとあるこのディレクトリが気持ち悪い
  • これと同じロジックを他のところでも使いたくなったときに、コピペが起きた

ということが起きました。
共通認識を形成できなかったのは単に布教が足りなかっただけなのですが、ディレクトリ構成はなんだかしっくりきていません。。。
皆さんのapp/view_objects以下はどんなノリになってるのでしょうか?
サービスの性質やシステム構成によって最適解は違うと思うのですが、おすすめのやり方があるぜという方もしいたらぜひ教えてください。

他にもいろんなPOROがいるのですが...

元ネタのコードを元ネタがわからないように改変するのがかなり時間かかって、力尽きてしまいました。

明日は@sadashiさんの「Androidアプリの設計 ~My Best Practice~」です。よろしくおねがいします!

35
24
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
35
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?