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