「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を使う意義が薄れてしまってつまらないから、適切な運用を心がければいいのだとおもう。