trailblazer/cells: View components for Ruby and Rails.
主に v2 以前から使っていた人向け。なんかずーっと ViewModel という言葉に引っかかっていたんだけど、ちょっと分かったかも。
まずは基本の使い方から。
定義
class SampleCell < Cell::ViewModel
..
end
呼び方
micro MVC 的な呼び方
cell(:<name>[, :<model>]).(:<state>[, options])
これで Cell の中のメソッドを読んだ結果を String として(to_sして)出力できる。View の中で micro MVC として使う Cells v2 以前の render_cell() 時代の使い方のイメージ。
ただのCellオブジェクトを取得する呼び方
cell(:<name>[, :<model>])
これで文字列になる前のただの初期化済み Cell オブジェクトが取得できる。
Cellの実体は.()でcallさせると安全にto_sできるオブジェクト
Cells は定義の部分よりは呼ぶ部分の方がちょっと気持ち悪い。まずマジカルさの一つは
cell().()
という見慣れない書き方なのだが、この .() は call() の syntax sugar だった。
Ruby | 「call」メソッド の Syntax Sugar 「.()」 について - Qiita
では call はどうなっているかというと、以下のようになっている。
def call(state=:show, *args, &block)
content = render_state(state, *args, &block)
content.to_s
end
call すると render して to_s している。1
さらに
def to_s
call
end
が定義されているので、 String として評価される context ではマジカルに #show が呼ばれ、view を render して何かが出力されるという算段になっている。
これを意図して rails generator で cell を作ると、まず
def show
render
end
だけを持つ class ができる。
ということで、「なんかいい具合に to_s するための仕組みの入っているオブジェクト」というのが Cells の実体と言ってよさそう。
一つ気をつけなきゃいけないのが、Cells は View ではなく Controller の context で実行されているということ。もう一つ、v4 以降の Cells は Rails 非依存なので、Controller を継承していない。cells-rails gem では関連 module はいろいろ include しているが、Controller を継承していることは前提にしてはいけない。
で、ViewModelとして扱うってどうよ?
Controller から View 向けのロジックを追い出すために Cells を使うとよいと「伝統的なサーバサイドWeb開発アンチパターンとその対策 - C, V編 - - あーありがち(2019-01-20)」に書いたが、Cells で本当によいのだろうか?という疑問は感じるかもしれない。むしろ PORO ( Plain Old Ruby Object ) の方がよいという意見もあるのではないかと思う。
結論から言うと PORO でもよいと思う。
ただ View 側に渡しやすい何かにするための ViewModel なはずなので、最初からそういう仕組みの用意されている Cells にしておけば自動的に置き場所も決まるし、安全に to_s されるし、独自の template を追加することもできるし、Helper も呼べるし、testability も考慮されてるし、まーだいたいよいことづくめだと思う。どうせ何らかの支援は欲しくなるはずだ。
個人的には Cell という名前だけが非常に引っかかっていて、どうしても micro MVC を用意するイメージが強いので、いっそのこと
app/cells/*_view_model.rb
みたいな名前付けで置いといて2、呼び方も
FooViewModel.new
みたいにしておくと、呼ぶ側からは明らかに render_cell の頃のアレとは違う使い方をしている感 が出るのでよいかもしれない。
これなら micro MVC 的な Cells と ViewModel を使い分けている感じをさせつつ、使っているツールは一つに絞れる。