サーバサイドViewModelのボキャブラリをクライアントサイドに寄せながら整理してみる

サーバサイドで ViewModel と呼ばれるもののボキャブラリがあまり整理されていないように見えるので、あれこれ調べたり書いたりしていた。そこでちょっと見えてきたものを挙げていきつつ、最終的には近年概念が大幅に整理されたクライアントサイドの ViewModel に寄せていくことで、荒れがちな View 由来のコード周りに秩序を与えられないか考えたメモ。

雑にパターン出し

サーバサイドの ViewModel という概念は以下のパターンで適用できそう。

  1. 変数の binding を担うもの
    • Hanami::Viewlaravel-view-models に相当
    • Rails では locals 縛りとかにしなければ不要なもの
    • View の直前でかつ Model としての複雑さはあまり考慮しなくてよいのでいわゆる Presenter と呼びたい
  2. 変数のセットまでの処理が複雑なもの
    • Fat Controllerの原因の一つを取り除く
    • たぶんこれは本来やったらダメなもの(正しく View 側の設計ができていない)
  3. state の複雑なコンポーネントを扱うもの

このうち 3 がクライアントサイドの ViewModel に近く、最も ViewModel っぽく機能しそう。これまで自分は View Objects という名前が全然しっくり来なくて馴染めなかったんだけど ViewModel と呼ばれるとよいかも。1

ちなみに単なるデータ構造を ViewModel というパターンもあるにはあるようだが、個人的にはそれは Model の Presentation (serialize)に相当すると思っているので、除外している。

サーバサイドViewModelはクライアントサイドのコンポーネントに対応できるのか?

Cells を例にとると、定義側は

cells/foo_cell.rb

class FooCell < Cell::ViewModel
  def bar
  end
end

cells/foo/bar.erb

<div class="cell">
</div>

みたいな感じになる。

これを呼び出す側が

@cell(:foo).(:bar)

こんな感じ。これで、呼び出すメソッドに応じて View も中の処理も切り替えることができる。

ここで疑問を覚えるのが、メソッド単位で View が変わるだけでは Component として複雑な状態を保持するには機能が足りないのではないか、ということである。

クライアントサイドの ViewModel Component は nest させることができ、悪名高き「propsバケツリレー」を使って state を受け渡す形を守ることで個々の Component のシンプルさを維持しつつ、複雑、大規模な機能、システムまでスケールすることを目指している。

サーバサイド ViewModel にクライアント ViewModel のその考え方を援用できるのだろうか。

RubyのCellsはnestできるしlayoutもある

trailblazer/cells: View components for Ruby and Rails.

Ruby の Cells については nest もできるし、layout もあり、またメソッドを state と(内部では)呼んでいるので、概ねクライアントサイドの ViewModel の考え方を援用できそうに見える。

例えば状態を3つ持つ Cell を作り、以下のように layout も用意してあげれば

layout

<div class="layout">
 <ul>
   <%= yield %>
 </ul>
</div>

cell の view

<li><%= complex_logic_results %></li>

最終的には

<div class="layout">
 <ul>
   <li></li>
   <li></li>
   <li></li>
 </ul>
</div>

こういう形のタブ構造などを簡単に実現できる。

このような形にできることが分かれば、Component の役割とどのような State を持つのかをモデリングすることで自ずと Cell の形が決まるし、形状、表現に変更が入ってもロジックやデータ構造にそこまでのダメージがないようにすることもできそうである。

Laravelのtorann/cellsの場合

ドキュメントにはそういう話はないので中のコードを読んだり実際に試してみると、

  • Layout の機能は CellBaseController にはないが、blade テンプレートの置き場所と名前付けにルールを設けることで layout のようなものは可能(layout の利用は @extends)
  • @cell の nest は @include の nest と同義なので特に問題ない

ということで やれば できそう。

cf. CellsはLayoutを持っているか - あーありがち(2019-01-27)

課題はロジックの分離とテスト

ViewModel として見た時にこれまで意識して来なかった課題が見えてくる。それは

  • ロジックを処理するメソッドと View を render するメソッドの区別がない

ことである。

例えば Vue.js を例に取ると、ViewModel コンポーネントは以下のような構造になっている。

{
  data:       <-- binding 用の data
  extends:    <-- 継承元
  components: <-- View で custom element として利用する component
  computed:   <-- メソッドベースの binding 用の data
  methods:    <-- event handler
}

このうち、 だいたい computed に判定用のメソッドを置く感じになる。2ということは computed をテストすると View のことを気にしなくてもロジックはテストできる。

しかし Cells の場合はメソッドと property しかなく、state / action はメソッドであり、これらを call すると render するのが基本である。これだとうっかりロジックを呼んだらそれが View に露出してしまう。幸い Ruby の Cells はまだテストも考慮されているが、torann/cells にはそれもない。

よくあるのは外から呼べないようにロジックの部分を private とかにしてしまう方法だが、それをやると今度はテストコードでテストできなくなる。

ではどうするか。

案1 - ロジックの結果を変数に放り込む

実際に torann/cells で書いてみた感じだと、property / attribute の方に「ロジックの結果を保持」するのがやりやすそうな気がした。すべてを state / data として扱って、初期化時にその前処理を終わらせる。template 側にロジックを書く際にも attribute はそのまま参照できるし、それっぽい名前の attribute であれば自然に読める。

「コンストラクタ頑張りすぎ問題」のようなにおいはするが、基本的にサーバサイドの ViewModel は副作用を扱う必要がないのでわざわざ階層を深くしなくても、メソッドの名前空間が render 処理に予約されているのであればメソッド以外を利用する、という考え方は一応、理には適っている。ような気がする。

案2 - 名前空間を独自に切る

例えば簡単な例だと

  • ロジックを扱うメソッドを _ で始める

というような形。

torann/cells の内部的にも $__env という名前で「頑張っている部分はある」し、_ や __ で始まるものに特別な意味を持たせるのは LL 界隈ではポピュラーな考え方だ。prefix を付けてよいというルールにすれば _ だけに限る必要もない。

案3 - ロジックをdelegateする新しいModelを作る

Ruby の Cells の場合は Model を一つ与えてそいつで delegate しつつ render するのが基本スタイルなので、その Model にロジックも Collection も全部押し付けることで ViewModel の名前空間で困らないようにすることはできる。

その場合、

app/cells/models/

みたいな階層を適当に掘ってみるのもアリなのではないだろうか。こうすれば間違いなくロジックはテストしやすくなる。

torann/cells の場合は Model object ではなく attributes array を受け取る設計なのでやや勝手が異なる。恐らく array の中に Model を押し込むことで似たようなことはできると思う。案2 とのハイブリッドのような形で、そこに Model を押し込んだことが分かる名前のルールがあれば可能そうだ。

とは言えコンポーネントの概念だけで全部解決することはなさそう

目的の明確なアプリを作るのであれば、できるだけ分かりやすくシンプルにすればよいと考えることができるし、「ユーザーは何らかの操作をしてくれる」という前提で補足的な説明は全部ポップアップなどに寄せてしまうということもできる。

しかしいわゆる Web サイトの場合は「提示しなければいけないデータ」の他に「事前に促しておくべき注意事項」などもあるはずで、アプリを構築する際に扱いやすい ViewModel の考え方に寄せるだけではやや解決の難しいものも出てきそうではある。

その場合は state / action という言葉にこだわらず View を提供しやすいという柔軟性に頼るとか、どうしても複数の Model の情報を渡したいという要求にどう応えるかを考えるなど、やはりフレームワークの提供してくれない解決策を探る必要はあるだろう。

  1. という気になるんだから自分はどこまで名前重要信者なんだろうか。 

  2. methods に置く場合もあるが、原則的に methods には副作用のあるものを置くはず。 

Ruby Gold Programmer になりました

ただしもう一回受けて合格する自信がありません(笑

実は12月に無理矢理時間を作って受けに行って落ちてます。準備は1週間程度。RUBY技術者認定試験 公式ガイド (ITpro BOOKs)1メタプログラミングRubyの第1章と @IT の模擬試験。結果、72点で不合格。

このときの感想としては

  • へー、プロメトリックの試験てこんなんなんだ(初めて受けた)
    • 15分前集合を守らないと受験できないのでとにかく行きの時間には余裕を持って!
    • 「あとで見直す」機能使いこなせなかった
    • 時計、筆記用具、携帯、すべて持ち込み不可。ハンカチ、ティッシュは可。パソコンの画面上で残り時間は確認できるし、メモ用のペンと薄いボードがあるので特に問題はない。
    • 確認画面を持参しろとプロメトリックのサイトには書いてあるんだけど、2回とも不要と言われた。
  • Silver の公式ガイドはよくできてる。これとリファレンスマニュアルだけで Silver は合格できるんじゃないかな。
  • メタプログラミングRubyはスバラシイ
  • @IT の模擬試験はやや簡単なものばかり出ている気がする(試したら90%以上は正解できていたので変に自信を持ってしまった。)
  • そんなに頻繁に起きないはらいたが直撃して超つらかった

プロメトリック試験は高いから避けていたのですが、あの方式の試験には慣れておいた方がいいと思いました。ちょっとビビる。それともイマドキの若い人はああいう試験慣れてるのかな。すべてパソコンで、隣の人は違う試験受けてて、ヘッドホンで遮音して、みたいな。1回目に受けたところはヘッドホンとは別に耳栓も用意してあったけど、2回目のところにはなかったので、試験会場としてはヘッドホンが必須要件なのかな? 頭がでかいのでああいうところのヘッドホンは苦手です><

勉強に使ったものでは、他の合格した方々も推しているメタプログラミングRubyが素晴らしいです。これでだいぶ上乗せできたし、今までなんとなく書いてた部分がすごく明確になりました。本当は「メタプログラミング」っていう言葉がなんとなくイヤで避けていたのですが、これは完全に損してましたね。今では特にテストのときにこの本の知識が役に立ってます。

ただ、1回目のまとめをすぐやらなかったのは完全に失敗でした。合格するつもりで受けるのは当然なのですが、終わった後にまとめる時間を用意できるようにしておくといいです。平日半休で受けるとまとめる時間がなくなって忘れちゃいます。

2回目の今回もけっきょく準備期間は1週間程度になってしまいました。リファレンスマニュアル中心に改めて基礎をまとめ直してたんですけど、結果はほとんど変わらなかったのでこれはあまりいい方法ではなかったみたい。むしろ Silver 向きの勉強方法だったかも。でもここでまたいくつも発見があったし、結果だけみれば合格だから、まぁいいかなぁ。本当はもっとライブラリ周り見た方がいいんだよなと思いながら基礎の Array や Hash をまとめ直していたら、案の定使っていないライブラリが出題されてしまいました。いやでも Array や Hash をどう使うとどういう例外が発生するとかも忘れてたし、やり直したことには意味はあります。きっと。

ちなみにピッケル本を読んでないので、それが敗因かもしれないし、そもそもコンパイラ作った経験とかあればそこから広げて類推できた部分もたくさんあったのかもしれません。2

正直、受かったはいいけど達成感が全然ないです。まだ全然 Ruby 分かってなさすぎる。まだまだだな!

認定試験を受けようと思った人へ

自分が今回の経験を通じて気づいたことをまとめておきます。何かの参考になれば。

  • Rubyインタプリタを自分の頭の中に作り上げられれば合格する
  • 使ってる時間と言語の習得レベルはまったく関係ない
  • なんとなく書けちゃう人、アプリや API などに強い関心を持っている人よりも真面目に Ruby を勉強する人の方がたぶんこの試験に向いてる
  • 常にググったり ri を引いていると基本的な文法やライブラリのメソッドをあまり覚えていない
  • 常に irb などでコードを動かして確認するクセがついていると細かい記述を覚えられない
  • なんとなくエラーの出る書き方を避けていると「ここではこういうエラーが起きる」ことを知る機会を失う
  • 使っていないライブラリのことなんかさっぱり知らない
  • ちゃんと勉強する時間を確保しよう。眠い頭で無理にやっても入らない。でもこれがいちばん難しいかも。
  • 準備の時間だけでなく振り返る時間も用意しておこう。もし落ちたらこの振り返りこそが次へのステップになる。

うーん、全然参考にならないな。

少なくとも自分は言語そのものにはあまり関心がないなーという自覚があったのですが、見事にそれがこの試験を受けるうえでは阻害要因になりました。すでに使えている人こそ一度気持ちをリセットして勉強し直すのがいいんじゃないかと思います。ただ、いろいろ合格した人のブログを見て回っていたのですが、Ruby を書いたことないけど合格してる人もいるんですよね…。どういう頭の構造をしているんだろう。不思議。

プロメトリックの試験会場はこの辺だと富山がよい

Ruby に限らずプロメトリックの試験は全部同じ会場で受けられます。ただし、当然ですが地域によって試験会場の揃い方にバラツキはあります。

金沢には2012-01時点で受験可能なところは一つしかなく、しかも交通の便は悪いです。3金沢市内に住んでいて車で移動できるならいいけど、能登から電車で来ますとかいう場合はなかなか大変。

その点、富山は駅から徒歩圏内に試験会場がありますし、何より土曜受験ができます。そうなんです。金沢の試験会場は土曜受験できないんです。これものすごく困ります。みんながみんな業務で受験できるわけじゃないんだぞ。

というわけで富山に近い方は富山での受験も考えてみていいと思います。

ただし、冬の試験はトイレが近くなってつらいです。

  1. だいぶ前に買うだけ買ってあった 

  2. 本当はそういうことを勉強できる道に進んでたはずだったなぁ、そういえば(遠い目) 

  3. そもそも金沢で交通の便のよい施設ってほぼ皆無ですけど 

rsync の読む .cvsignore は source DIR のもの

rsync は -C オプションで CVS ディレクトリや .cvsignore に書かれているものなど CVS と同じルールで、コピーの際にファイル、ディレクトリ群を無視することができる。

なんだけど、この .cvsignore の動作がよく分かっていなかったようで、ちゃんと無視されずに転送されてしまうことがあった。

man rsync より

             then,  files  listed in a $HOME/.cvsignore are added to the list
             and any files listed in the CVSIGNORE environment variable  (all
             cvsignore names are delimited by whitespace).

             Finally, any file is ignored if it is in the same directory as a
             .cvsignore file and matches one of the patterns listed  therein.
             Unlike rsync's filter/exclude files, these patterns are split on
             whitespace.  See the cvs(1) manual for more information.

えーと。

  • ホームディレクトリの .cvsignore
  • コピー対象となっているディレクトリにある .cvsignore

の中に記されているファイル、ディレクトリ群が exclude される。

ってことかな?

gem の検索って不便だな

RubyGems の gem コマンドでパッケージを検索するには

gem search STRING [options]

とするわけなんだけど、これってパッケージ名にしか引っかからないんだなぁ。だとするとあんまり使えない。query とか list とかあるけど結局 name しか見てないから、「こんな機能を持ってるパッケージがあるかどうか」は raa や rubyforge の Web 上のインターフェイスから確認しないとよく分からんてことになっちゃう。(Fink で入れた 0.8.11 と ports で入れた 0.9.0 で確認。)

あ。

gem list --remote | grep -i KEYWORD

にすればいいのか。と思ったら description の行しか引っかからなくて訳分かんねー。

gem list --remote | grep -3 -i KEYWORD

とかになるのか。面倒くさいなぁ1。おまけに遅いし。apt みたいにローカルにキャッシュ作ってそこから検索してくれないかな。それともそういう gem パッケージがあったりするのかな。gem list でドバーっと取れるリストを DB に突っ込んでそこから検索してくれるようなやつ。

gem list --remote | grep -3 -i KEYWORD | grep '^[^[:space:]\-]'

にするとなんとなくほしい情報に近い感じになるけど、こうなると列挙されるバージョン番号が邪魔だし、本来探しているのと異なるパッケージが出てる可能性もある。現状の list の出力からだと加工も面倒だと、そういうことだなきっと。

あー gem specification の YAML 出力と組み合わせれば目的のものができそう。できそうなのは分かった。……。うん、できそうなのは分かったよ。んー。でも spec を remote から取得する方法はないのか。結局そこに行き着くんだな。gem_server ミラーリング? うえー。

  1. ports の make search key=KEYWORD | grep ^Path もかなり面倒だが、これはまだほしい情報が確実に取れるからかなりマシ。 

coLinux

http://www.colinux.org/

Windows のドライバレベルで Linux カーネルを動かすぞプロジェクト。userland もバイナリ互換があるそうで、ソースレベルの(しかも完璧ではない)互換性なのに ports のようにソースで最新版を追いかけられる仕組みを用意してない cygwin の使いにくさが一気に解消されてチョーはっぴーって感じでしょうか?(ほんとか?)

cygwin は CUI でのアップデート方法が用意されていればもう少し扱いが楽になるんですがねぇ。今の setup.exe はインストールはいいけどアップデートが面倒すぎます。

あと、cygwin は RedHat の方針がどうなるかよく分からないので、そのときのためにも coLinux が早く安定すると嬉しいですな。

日本の情報投資額、実はバブル期の1.5倍、そのうち9割が無駄

@CNET Japan

そりゃー決済権を持ってる人がすでに現場に居ないから管理系のバックオフィスにまず投資するって形になるんじゃないですか?

About

例によって個人のなんちゃらです