読者です 読者をやめる 読者になる 読者になる

MVC, MVAC, Module, Trait

「Modelは永続化されたデータだけじゃなくて、ドメインロジックも扱う抽象的なデータだよ」とか言われる、だいたいこの後に「だからControllerは薄く、Modelは太らせよう (Skinny Controller, Fat Model)」とか続く。

ControllerだろうがModelだろうが、太ってテスタビリティが失われればどのみち破綻してしまうし、だからこそMVACという考え方が出てきたりするわけで。

Modelにドメインごとのロジックを継ぎ足すという考え方はできないだろうか、っていうのを考えている。

たとえば、UserというModelがあって、UserはVideoを5つアップロードすることができる、課金したプレミアム会員だと100個までアップロードできる、みたいなのを考える。

class User
  include Mongoid::Document

  field :first_name, type: String
  field :last_name, type: String
  field :premium, type: Boolean

  has_many :videos
end

class Video
  include Mongoid::Document

  field :title, type: String
  field :rating, type: Float

  belongs_to :user
end

こんなの。

たとえば「ユーザのフルネーム」というのは「ユーザ」という領域に属するロジック (というほどのものでもないけど) だから、Userに書く。

class User
  # ...
  def full_name
    [first_name, last_name].join(' ')
  end
end

ではアップロードの処理はどうか。これを「動画をアップロードできる権限をもったユーザ」という具象的なModelとして捉えて、Moduleとして表現する。

module User::Uploader
  def upload_video(video)
    # ...
  end
end

class User
  # ...
  include User::Uploader
end

課金の有無によってアップロードできる動画の数が変わってくる。プレミアムユーザというロールも作ったほうがよさそう。

module User::Premium
  field :last_paid_at, type: DateTime
end

class User
  # ...
  after_initialize :premiumize, if: -> user { user.paid? }

  private

  def premiumize
    extend User::Premium
  end
end

動的なRubyっぽいやりかたになった。こうするとなにがおもしろいかというと、権限が複数ありえるときにcase文でいいかんじに書ける。

case @user
when Teacher
  # ...
when Protector
  # ...
when Student
  #...
end

列挙型を使わずに、オブジェクトが持つ役割、機能をベースに判断するというのは、動的言語っぽくていいとおもう。

module User::Uploader
  # ...
  def max_uploadable_videos_count
    paid? ? 100 : 5
  end

  def uploadable_videos_count
    max_uploadable_videos_count - videos.count
  end
end

とか。

黒魔術に染まるとひどいことになるからほどほどにというかんじだけど、恐れてまったく使わないでいるのもRubyを使う意義が薄れてしまってつまらないから、適切な運用を心がければいいのだとおもう。